I'm automating e2e tests on an angular app. We have virtual scrolls on few input elements (only 10-20 items are loaded at a time, scrolling down loads more items and delete the previous ones to improve performance).
I made a method to scroll until I find the DOM element and click on it. However, I don't know how I can stop looping after I clicked the element.
Here is what I've done so far :
findQuery(queryName){
this.getSavedQueryCount().then((queryCount)=>{ // Total nb of items
this.count().then((displayCount)=>{ //nb of items this screen can display
let count = 0;
let flag = 0;
let actualCount = displayCount + 1;
do
{
if(count % (actualCount) === 0 && count !== 0) count++; //handling weird behavior, index 0 never gets deleted so we increment by 1 to get the next item.
browser.executeScript('arguments[0].scrollIntoView({behavior: "smooth", block: "end"});', this.getSavedQuery(count % actualCount)); // scroll to elem at index
this.getSavedQueryByName(queryName).isPresent().then((bool)=>{ // clicking on elem if it's in the DOM
if(bool)
{
this.getSavedQueryByName(queryName).getAttribute('class').then((cl)=>{
if(!(cl === "selected"))//making sure to click only once so we don't deselect the item
{
this.getSavedQueryByName(queryName).click();
flag = 1;
}
});
}
});
count += 1;
} while(queryCount > count || flag);
});
});
}
Please don't judge the code I will refactor it after I get the answer to this question (don't take the weird index handling into account).
So far this method works but it never stops looping until queryCount > count. I want it to stop looping after I clicked the element too (with the flag var).
Thanks in advance.
Related
I'm writing a choose your own adventure program where If a specific option is chosen (example to wait) the user gets a random number between 1-10 to do push ups(the push-ups would be the user clicking on the prompt "ok" button however many times the random number is equal to) here's my code so far but I keep getting errors. I'm a complete noob so go easy on me.
var count = Math.floor((Math.random() * 10) + 1);
var setsOf10 = false;
function pushUps() {
alert("Nice! Lets see you crank out " + pushUps + "!");
}
if (setsOf10 == pushUp) {
alert("Nice! Lets see you crank out " + pushUp + "!");
setsOf10 = true;
}
for (var i=0; i<count; i++){
pushUps();
}
else {
alert("Really, thats it? Try again");
}
while ( setsOf10 == false);
}
After playing with this some more I can tell i'm close but still don't have it. and again, I'M NOT ASKING YOU TO SOLVE THIS FOR ME JUST NEED POINTERS AS TO WHAT IM DOING WRONG OR MISSING. Here's what I have, Its giving me my random number I just need it to allow me to click the "ok" button however many times the random number has assigned me.
var pushUpSets = Math.floor((Math.random() * 10) + 1);
function pushUps(){
alert(pushUpSets);
if (pushUpSets < 3){
var weak = "Thats it? Weak sauce!";
alert(weak);
}
else{
alert("Sweet lets get some reps in!");
}
for (i=0; i>3; i++){
pushUps(pushUpSets);
}
}
Here, the make a choice button is just dummy to allow us to go to do push ups. Each click decrements our count.
// This is important, we use this event to wait and let the HTML (DOM) load
// before we go ahead and code.
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('#choice').addEventListener('click', makeChoice);
});
function makeChoice() {
// Call a method to set random pushups and setup the click event
setUpPushUp();
// Here we change the display style of the push up section so that it shows to the player.
document.querySelector('.activity').style.display = 'block';
}
// The pushups variable is declared at the document level
// This way our setUpPushUp and doPushUp functions have easy access.
let pushUps = 0;
function setUpPushUp() {
// Create a random number of pushups, in sets of 10.
// We add an extra 1 so we can call the doPushUp method to initialize.
pushUps = (Math.floor((Math.random() * 10)+1)*10)+1 ;
// Add a click event to the push up button and call our doPushUp method on each click.
document.querySelector('#push').addEventListener('click', doPushUp);
// This is just an init call, it will use the extra 1 we added and place test in our P tag.
doPushUp();
}
function doPushUp() {
// Get a reference to our output element, we will put text to player here.
let result = document.querySelector('p');
// They have clicked, so remove a push up.
pushUps--;
// See if the player has done all the required push ups (i.e. pushUps is 0 or less.)
if (pushUps > 0) {
result.innerText = `You need to crank out ${pushUps} pushUps`;
} else {
result.innerText = 'Nice work!';
}
}
.activity {
display: none;
}
<button id="choice">Make a choice !</button>
<div class="activity">
<p></p>
<button id="push">Push</button>
</div>
I have a page that has JavaScript that executes history.go(-1); return false; to go to a previous page when a "Return" button is pushed. The page displays the correct selected option on the initial page load but when going to the next page that has the "Return" button and then going back, the selected option displayed on the screen becomes a random value that is not the same as what the DOM shows. Below are screenshots of the progression and then my sortSelect() code at the bottom. This issue does not occur in Edge or Chrome but does in IE 11.
I'm ultimately wanting to know how to remedy this issue such that what is displayed on the screen reflects what the DOM is saying is the selected option.
Before Sort on initial page load (correct option displays):
After Sort on initial page load (correct option displays):
Before Sort after history.go(-1) from next page (incorrect option displays):
After Sort after history.go(-1) from next page (incorrect option displays):
Sort Select method:
$.fn.sortSelect = function() {
this.each(function(index, selectElement) {
if (selectElement && selectElement.tagName.toUpperCase() === "SELECT") {
var val = $(selectElement).val();
// get the child options
var options = $(selectElement).children('option');
// sort'em
options.sort(function(a, b) {
if ( ! a.text) {
a.text = "";
}
if ( ! b.text) {
b.text = "";
}
if (a.text.toLowerCase() > b.text.toLowerCase()) {
return 1;
} else if (a.text.toLowerCase() < b.text.toLowerCase()) {
return -1;
} else {
return 0;
}
});
// purge the select element and append the sorted options.
$(selectElement).empty();
$(selectElement).append(options);
$(selectElement).val(val);
}
});
};
I've written a stacking function with jQuery that takes container element .search-results, gathers all the .card elements within each .col element in the container and then restacks them across a number of columns, which are calculated based off the width of the container.
When the page is loaded, all cards are initialized in the first column and subsequent columns are hidden. The script then unhides however many columns it determines should exist and iterates through the columns and cards, using appendTo to evenly distribute the cards amongst the columns.
I've created a jsFiddle that demonstrates the process.
The script works great when it's called on page load. If I want to call the script after the page has loaded though, I encounter a serious problem. If I remove one of the cards and want to restack them so the distribution remains even, I must call the stacking function again. When I do so, it causes the embedded content (e.g. embedded Tweets) within the cards to be reloaded, which is undesirable. Note that when the page loads the Tweets are not initialized until after the stacking function has been called.
In my example I intentionally iterate over the tweet ids to reinitialize them using twttr.widgets.createTweet(), however if I omit that step, the Tweets disappear after the cards are restacked. If I inspect the cards I can see that the Tweet widget iframe is still present, but the iframe's body is empty.
Now obviously I could simply reinitialize my Tweets after I restack the cards but that would offer up a poor user experience since there is a delay when (re)initializing an embedded Tweet. I had previously posed a question related to this. I found that I can manually move cards around from the console using .clone() and appendTo() without reloading the embedded Tweets, however I've not had any luck refactoring my stacking function to take advantage of this behavior, which is why I'm asking this question.
Here is my stacking function:
function resizeColumns(layoutElem, ignoreRank, width) {
if (width === undefined) {
var colMin = 300;
} else {
var colMin = width;
}
var w = layoutElem.width();
var numCols = Math.floor(w / colMin);
if (numCols === 0) numCols = 1;
layoutElem.removeClass('cols-1 cols-2 cols-3 cols-4 cols-5 cols-6 cols-7 cols-8 cols-9 cols-10 cols-11 cols-12');
layoutElem.addClass('cols-' + numCols);
var cols = layoutElem.find('.col');
var cards = cols.find('.card');
var sortedCards = [];
cards.each(function(i) {
var rank;
if (!ignoreRank) {
rank = parseInt($(this).attr('rank'));
if (isNaN(rank)) rank = 1000000;
} else {
var o = parseInt($(this).attr('order'));
if (isNaN(o)) {
rank = i;
$(this).attr('order', i);
} else {
rank = o;
}
}
sortedCards.push({
rank: rank,
element: $(this)
});
});
sortedCards.sort(rankCompare);
var curCol = 0;
for (var i in sortedCards) {
var cardElem = sortedCards[i].element;
cardElem.appendTo($(cols[curCol]));
curCol++;
if (curCol >= numCols) curCol = 0;
}
// hide any additional columns
cols.each(function(i) {
if (i >= numCols) {
$(this).hide();
} else {
$(this).show();
}
});
function rankCompare(a, b) {
if (a.rank < b.rank)
return -1;
if (a.rank > b.rank)
return 1;
return 0;
}
}
Steps I take to stack the cards and initialize the Tweets in the jsFiddle
The first time I call the function I introduce a delay so that $scope.cards is defined. Here is the first call:
$timeout(function() {
// Only the first argument is required.
resizeColumns($('.cards'), true, 250);
}, 250)
After I call the resizeColumns function I set a delay before initializing the tweets for the first time. This is essential so that the Twitter widgets script can locate the Tweets in the DOM.
Once the Tweets are initialized, I set a delay of 4s before removing a card and restacking the cards. Finally, once the cards are restacked, I reinitialize the Tweets. This last step is what I want to avoid. I don't want to have to reinitialize the Tweets because it's a slow process. I want the Tweet to reappear instantly once the card is moved.
$timeout(function() {
// Iterate over the Tweet IDs and initialize them.
ids.forEach(function(id) {
twttr.widgets.createTweet(id, document.getElementById(id));
});
$timeout(function() {
// Wait a few seconds and then remove a card from the DOM.
$('#' + $scope.cards[1].id).detach();
// Restack cards
resizeColumns($('.cards'), true, 250);
$timeout(function() {
// Reinitialize Tweets after cards have been restacked. This is the step I want to avoid.
ids.forEach(function(id) {
twttr.widgets.createTweet(id, document.getElementById(id));
});
}, 500)
}, 4000);
}, 1000);
Based on the answer to the other question, try changing:
sortedCards.push({
rank: rank,
element: $(this)
});
to:
sortedCards.push({
rank: rank,
element: $(this).clone();
});
$(this).detach();
Or you could do the clone and detach in the appendTo loop:
for (var i in sortedCards) {
var cardElem = sortedCards[i].element.clone();
cardElem.appendTo($(cols[curCol]));
sortedCards[i].element.detach()
curCol = (curCol + 1) % numCols;
}
My carousel has been built so that it slides to the next frame automatically every 5 seconds, for which I have written:
var carousel = $('.carousel ul:first');
var pagination = $('.carousel ul:last');
function slide01() {
carousel.delay(5000).animate({'margin-left':'-100%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(2)').addClass('on');
});
slide02();
};
function slide02() {
carousel.delay(5000).animate({'margin-left':'-200%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(3)').addClass('on');
});
slide03();
};
function slide03() {
carousel.delay(5000).animate({'margin-left':'-300%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(4)').addClass('on');
});
slide04();
};
function slide04() {
carousel.delay(5000).animate({'margin-left':'0%'}, function() {
pagination.find('li').removeClass('on');
pagination.find(':nth-child(1)').addClass('on');
});
slide01();
};
slide01();
While this is working, I'm having difficulties with the pagination buttons. They respond but instead of going to its appropriate page, it's going to the next page, which you can view in my FIDDLE.
This is the code I have written for each button:
pagination.find('li:nth-child(1)').click(function(){
carousel.stop().animate({'margin-left':'0%'});
});
pagination.find('li:nth-child(2)').click(function(){
carousel.stop().animate({'margin-left':'100%'});
});
pagination.find('li:nth-child(3)').click(function(){
carousel.stop().animate({'margin-left':'200%'});
});
pagination.find('li:nth-child(4)').click(function(){
carousel.stop().animate({'margin-left':'300%'});
});
What can I do to fix this?
I have updated your Javascript code to provide your desired functionality and made it more extendable. Please let me know if you have any questions.
var carousel = $('.carousel ul:first');
var pagination = $('.carousel ul:last');
var slideWidth = 200;
var autoSlideTiming = 5000;
var timeout = null;
// Buttons
$.each(pagination.children(), function(i){
$(this).click(function(){
pagination.find('li.on').removeClass('on');
//if you wanted to find the width dynamically
//var slideWidth = $(this).parent().parent().find('ul:eq(0) li:eq('+i+')').width();
carousel.stop().animate({'margin-left': -(i * slideWidth)});
$(this).addClass('on');
});
});
// Slider
function advanceSlide(){
var currIndex = $(".pagination li.on").index();
var nextIndex = ((pagination.children().length - 1) == currIndex) ? 0 : (currIndex + 1);
carousel.animate({'margin-left': -(nextIndex * slideWidth)}, function() {
pagination.find('li.on').removeClass('on');
pagination.find(':nth-child('+(nextIndex + 1)+')').addClass('on');
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
});
}
$('.carousel').hover(function(){
clearTimeout(timeout);
}, function(){
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
});
//init auto slide
timeout = setTimeout(function(){advanceSlide()}, autoSlideTiming);
Also here is an updated Fiddle
Explanation
The $.each is a jQuery utility function that works like a normal for loop (I would actually recommend a vanilla JavaScript for loop if you know how). This $.each accepts 2 arguments: a collection (pagination.children() in our case, which is an array of the li's), and a callback function. This callback function passes in i which is our zero-based index of the collection. So, it's not 1, 2, 3, 4 but 0, 1, 2, 3. In this loop this is each li, which it is attaching a click event that handles removing and applying your on class and also the animation. The -(i * slideWidth) is taking the current zero-based index and multiplying it times the slideWidth and then getting the negative value of that result. So, the first slide -(0 * 200) would animate the margin-left to -0 or 0, and the second slide -(1 * 200) would animate the margin-left to -200, which pulls it 200 pixels in the left direction making it slide. This will allow you to had new li's to your pagination and this code will still work!
The trick to making the advanceSlide() function dynamic, is finding what the index of the next slide is. To find out where we need to go we first must find where we are. That is what currIndex is for, which we find with the jQuery index() function, which returns the zero-based index of the li with the on class. Now the magic. The nextIndex variable is being set using a ternary operator to find the value. A ternary is just programmer shorthand for a basic if then else statement. So if the ternary in this code was written out the normal way it would look like this:
var nextIndex;
if( (pagination.children().length - 1) == currIndex ){
nextIndex = 0;
}else{
nextIndex = currIndex + 1;
}
Remeber our currIndex is zero-based, but our collection (pagination.children()) length (number of items in the total collection) is not zero-based so we must minus 1 from it. Then we are checking if that value is equal to our currIndex because if this is the last item in our collection we need to set our nextIndex back to 0 so the slideshow loops back around. If it's not the last index we just add 1 to advance to the next slide. After we find that nextIndex we do our animation and apply our class.
I know this is a little long winded, but it's important that you fully understand the code you implement! Please let me know if you have nay further questions!
You have a "Maximum call stack size exceeded." error. Yours slide0X function calls another slide0Y function and so on. You should invoke slide0Y only once the slide0X animation ended (inside the complete callback).
Moreover in your click callbacks, margin-left should be negative since you want to slide to the left.
BTW, I would implement a function slideTo(slideId) so that it will work for n slides, instead of writing a function for each slide.
Essentially I want to use a button to bring a div to the front using the CSS z-index and then when pressing the button again I want it to revert back to its original state.
This is the code I have got so far and it will happily change it first time round but it wont revert it back.
function thumbnail(){
if (document.getElementById("div").style.zIndex= -3){
document.getElementById("div").style.zIndex= -2;
}
if (document.getElementById("div").style.zIndex= -2){
document.getElementById("div").style.zIndex= -3;
}
}
function thumbnail(){
var depth = document.getElementById("div").style.zIndex;
document.getElementById("div").style.zIndex = (depth == -3)? -2 : -3;
}
This slightly more efficient version avoids traversing the DOM more than once:
function thumbnail () {
var elStyle = document.getElementById ("div").style;
elStyle.zIndex = elStyle.zIndex == -3 ? -2 : -3;
}
Using div as an id may prove to be confusing when you return to this code later, use a name which identifies the function of the div.