How reduce delay in javascript / jquery change event? - javascript

I have five filters (multiple selects), which filter the data behind five visualisations in a dashboard. The filters have a JQuery change event, but the first line of code in this event takes half a second to happen. I don't understand why there's a delay. If I remove the code after the first line of code, the delay goes away. It's as though the code is not running in sequence.
The purpose of that first line of code is to make visible five "misty" (semi opaque) divs to obscure the graphics until the update code has run.
I'm using chosen.js on the selects but even when I remove chosen, there is still a delay. The filters are built dynamically. Here's the code that adds the change event:
for (i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change',function(e,p){
d3.selectAll("div.misty").style("visibility","visible");//make the fade divs appear - takes half a second
if (!p) {
for (var j=0; j<filters[0].length; j++) { filters[6][j] = []; filters[5][j].filterAll(); }
} else {
if (p.selected) {
var tempIndex = filters[0].indexOf(e.target.id);//whether it's company, portfolio, industry or country
filters[6][tempIndex].push(p.selected);//store this filter
filters[5][tempIndex].filterFunction(function(d){ return filters[6][tempIndex].indexOf(d)!=-1; });
}
if (p.deselected) {
var tempIndex = filters[0].indexOf(e.target.id);//whether it's company, portfolio, industry or country
var tempIndex2 = filters[6][tempIndex].indexOf(String(p.deselected));
filters[6][tempIndex].splice(tempIndex2,1);
filters[5][tempIndex].filterAll();
if (filters[6][tempIndex].length>0) { filters[5][tempIndex].filterFunction(function(d){ return filters[6][tempIndex].indexOf(d)!=-1; }); }
window.portfolio_p = window.portfolio_p2;
}
}
update();
})
}
If I remove the update commands, the code runs much quicker:
for (i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change',function(e,p){
d3.selectAll("div.misty").style("visibility","visible");//make the fade divs appear - takes half a second
}

Mmm, I have to agree you have an odd bug.
All I can suggest is pushing the filter manipulation into a later event thread.
You could contrive to use a Promise but window.setTimeout() is less expensive.
for(i=0; i<filters0Length; i++) {
$("[id='"+filters[0][i]+"']").on('change', function(e, p) {
d3.selectAll('div.misty').style('visibility', 'visible');
window.setTimeout(function() {
if (!p) {
// etc...
} else {
// etc...
}
update();
}, 0); // in practice the delay will be something like 15 ms.
})
}

Related

Why are old variables reused when using event handler functions with Javascript?

I'm trying to create a Simon game (project from a Udemy course) where you have to memorize a pattern sequence and click the right buttons. At each level, the pattern sequence increases by one. You have to repeat the entire sequence each time. If you lose, you have to press a key to restart the game. Here is an example:
https://londonappbrewery.github.io/Simon-Game/
However, when my game is restarted with my current code, everything falls apart. I don't know if old variables from a previous game continue into the new one, or maybe my code is just written really poorly.
I've implemented the entire game within a keypress event handler. Doing this initiates all the functions and variables of the game. The sequence is stored within an array, and each subsequent click on a box compares its colour value against the array. If it doesn't match - its game over. If it does match, it will either wait for you to finish the sequence correctly, or go to the next level.
Can anyone give me pointers on what I might be doing wrong? And whether it's at all possible to make this work the way I have it set up?
$(document).on("keypress", function(){
let counter = 0;
let level = 1;
let colours = ["green","red","yellow","blue"];
let nodeList = [];
function nextNode(){
randomNumberGen = Math.floor(Math.random()*4);
currentNode = colours[randomNumberGen];
nodeList.push(currentNode);
$("."+currentNode).fadeOut(200).fadeIn(200);
}
$("h1").text("Level "+level);
setTimeout(function() {
nextNode();
},500);
$(document).on("click", ".box", function(){
selectedNode = $(this).attr("id");
$("#"+selectedNode).addClass("pressed");
setTimeout(function(){
$("#"+selectedNode).removeClass("pressed");
},100);
if (nodeList[counter] != selectedNode){
gameSounds("wrong");
$("body").addClass("game-over");
setTimeout(function(){
$("body").removeClass("game-over");
},100);
$("h1").text("Game Over, Press Any Key to Restart");
}
else{
gameSounds(selectedNode);
counter++;
if (counter>= nodeList.length){
level++;
setTimeout(function(){
$("h1").text("Level "+(level));
}, 1000);
counter = 0;
setTimeout(function() {
nextNode();
},1000);
}
}
});
});
function gameSounds(key){
var soundPlay = new Audio("sounds/"+key+".mp3");
soundPlay.play();
}

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

Is it possible to pause a while loop until an animation finishes?

I'm trying to run a while loop that contains an animation. What I'd like to happen is for the while loop to pause, let the animation finish, then resume.
This is not my actual code, but it gets to the issue, I believe:
var counter = 0;
while (counter < 2) {
$(".one").animate({"left":"+=50px"}, "slow", function() {
counter++;
});
};
This crashes my browser because it doesn't wait for the animation to finish (and consequently it doesn't wait for the counter to increase) before it continues through the while loop. Thanks in advance!
https://jsfiddle.net/uhmctey6/
EDIT
Thanks everyone for explaining why this is impossible. I'm still unsure how to do what I need, however, and since I didn't use my actual code for the example, I'm not sure if the suggested solutions could help.
Here is what I'm actually trying to do: I'm making a turing machine with a reader and a array of cells that it reads. Upon running this function, I'd like to search through a list of turing code lines to see if one matches the readers current state and the content of the current cell that the reader is scanning. If there's a match, I'd like for the reader to make a series of changes specified by the relevant turing code line, then visually move over to the next cell, and only after this animation has completed start the process over again by searching through the list of turing code lines to see if there is a match for the reader's new state, etc.
I understand that this can't be achieved as I have it, using a while loop, but is there a way to do something like this another way? Thanks a lot!
var run_program = function() {
while (true) {
for (var j=0; j< program.length; j++) { //loops through the different lines of turingcode
if (reader.state === program[j].state && reader.scanning === program[j].scanning) { //if there is a line of turingcode for the readers current state and scanning values.
cellArray[reader.location].content = program[j].print; //change the content of the current cell to the new value
reader.location += 1; //increase the value of the reader's location
$(".reader").animate({"left":"+=50px"}, "slow"); //move the div to the right so it appears to be under the next cell
reader.scanning = cellArray[reader.location].content; //update the readers scanning value to be the value of the new cell
reader.state = program[j].next_state; // update the state of the reader to that specified by the line of turingcode
break;
}
else if (j === $scope.program.length-1) { //if there is no line of turingcode for the readers current state and scanning values, and j has looped through the entire list of turingcode lines
return; //halt the function
}
}
}
}
A for-loop/while-loop can run, skip and jump - but not stand still.
In other words, you cannot run asynchronous code affectively inside a synchronous loop. See this question/answer for more on that.
In the meantime, it looks like you want to run an animation a couple of times in sequence. I believe that jQuery can queue effects like animate, so it could be as simple as chaining the two calls to animate:
$(".one")
.animate({"left":"+=50px"}, "slow")
.animate({"left":"+=50px"}, "slow"); /* animate twice */
.one {
position: absolute;
width: 50px;
height: 50px;
background-color: green;
left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="one"></div>
Update
In response to your edit, it seems like what you want to do is turn a heavily synchronous piece of code into one that can accommodate some occasionally asynchronous behaviours.
Unfortunately, your example is unworkable for me because it has no data and context. However, I've given it a stab. Below is, untested, how I would turn your code into something as you've described in the edit of your question. I hope it helps.
var run_program = function() {
next(0);
// read a particular iteration
function next (j) {
if (j < program.length) {
// if there is a match, stop iterating and run a method which calls next when it's finished
if (reader.state === program[j].state && reader.scanning === program[j].scanning) {
turing(j);
} else {
// if there is no turing code then next is called instantly
next(j + 1);
}
}
}
// process a line of turing code
function turing (j) {
cellArray[reader.location].content = program[j].print;
reader.location += 1;
reader.scanning = cellArray[reader.location].content;
reader.state = program[j].next_state;
// may as well run this last as it is asynchronous
$(".reader").animate({"left":"+=50px"}, "slow", function () {
// next is called when the animation is finished
next(j + 1);
});
}
}
This won't work for the reasons stated in the other answers. An alternative option is to use conditional recursion as such:
var counter = 0;
function myAnimation () {
$(".one").animate({"left":"+=50px"}, "slow", function () {
counter++;
if (counter < 2) {
myAnimation();
}
});
}

Best Way to Hide/Show Multiple Surfaces with RenderControllers

What's the best practice to show/hide multiple surfaces simultaneously? Do I group all the surfaces under one render controller? Or, does each surface have a render controller assigned to it?
I am currently doing the latter, but am left with the distinct difficulty of attempting to trigger follow-up transitions. This is hard for me because my implementation doesn't provide a clear indication of when all the surfaces have been hidden. It is even harder because the hide transition is triggered with a bounded random time interval (between 200 to 2000 millisecond).
Any solutions? Code below:
for (var i = 0; i < surfaces.length; i += 1) {
var surface = surfaces[i][0];
var renderController = surfaces[i][1];
if (s.id !== clickedSurface.id) {
var fn = (function (s, rc) {
return function () { Timer.setTimeout(function () {rc.hide()}, getRandomArbitrary(200,2000)); };
})(surface, renderController);
s.colored ? Timer.setTimeout(fn, 2500) : fn();
}
}
If you wanted to iterate through your surfaces and hide one at a time, you could do something like the following code shows.
Example jsBin Here
function _hideNext(index) {
if (index === surfaces.length) {
//do something final, now complete
_showNext(0);
} else {
var rc = surfaces[index][1];
var surface = surfaces[index][0];
var nextIndex = index + 1;
rc.hide(surface, _hideNext.bind(this, nextIndex));
}
}
_hideNext(0);
Just change your RenderController options to your needs.

Trouble with OOJS

I have a variable number of sliders on a page. Each slider contains a variable number of slides, and a pair of prev/next buttons to navigate sequentially through its respective slides.
Here is the markup for a typical slider:
<section class="foo">
<button class="prev"></button>
<ul class="container slider">
<li class="slide" id="a01"></li>
<li class="slide" id="a02"></li>
<li class="slide" id="a03"></li>
</ul>
<button class="next"></button>
</section>
Each slider should know the index of the slide it is currently showing. This leads me to believe that on page load, I should loop through all $('.slider') elements and instantiate an object for each. How do I write the class that defines the slider? It should have a property that contains the index of the current slide being shown, and a method that increments or decrements that property depending on whether the user clicked 'next' or 'prev'. I've come as far as this:
var slider = function () {
this.pager = 0;
this.incrementPager = function () {
console.log(this.pager++);
};
};
$(function () {
$('.prev, .next').click(slider);
});
... But my code doesn't do much :)
Here's how to make a jQuery plugin, which I think is a better way since you're using jQuery, which is not really meant for Class based OO.
$.fn.slider = function() {
$(this).each(function() {
var wrapper = $(this);
var index = 0;
var listItems = wrapper.find('.slide');
wrapper.find('.prev').click(function(){
if (index > 0) {
$(listItems[index]).removeClass('active');
index--;
$(listItems[index]).addClass('active');
}
});
wrapper.find('.next').click(function(){
if (index < listItems.length - 1) {
$(listItems[index]).removeClass('active');
index++;
$(listItems[index]).addClass('active');
}
});
});
};
And you could use it like
$('section').slider();
Working Example http://jsfiddle.net/MR8wE/
If you really want some OO in there you could do the following (but I think it goes against how jQuery works).
/**
* Adds behavior to the HTML for a slider
*/
function Slider(wrapper) {
var me = this;
me.wrapper = $(wrapper);
me.index = 0;
me.listItems = wrapper.find('.slide');
wrapper.find('.prev').click(function(){
me.prev();
});
wrapper.find('.next').click(function(){
me.next();
});
}
Slider.prototype.next = function() {
this.go(1;)
}
Slider.prototype.previous = function() {
this.go(-1);
}
Slider.prototype.go = function(steps) {
var oldIndex = this.index;
var newIndex = oldIndex + steps
if (newIndex >= 0 && newIndex < this.listItems.length ) {
$(this.listItems[oldIndex]).removeClass('active');
$(listItems[new]).addClass('active');
this.index = newIndex;
}
}
Then you would use it like
var sliders = [];
$('section').each(function(){
sliders.push(new Slider(this));
});
// Control the slider without user intervention
sliders[0].next();
sliders[1].go(2);
Example http://jsfiddle.net/MR8wE/2/
In response to the question in the comments:
function Slider(elm)
{
this.domElement = elm;
this.pager = 0;
}
Slider.prototype.incrementPager = function()
{
this.pager++;
};//all instances share this method ==> only 1 function object in mem.
$(function()
{
var objectCollection = (function(elements)
{
var toReturn = [];
var scanElementsFunction = function()
{//declared in this scope, to have access to elements var
'use strict';//avoid setting globals
if (this.allElems === undefined)
{
this.allElems = elements;
}
return this.allElems;
};
elements.each(function(idx,el)
{
toReturn[idx] = new Slider(el);//<-- pass DOMElement to constructor
toReturn[idx].getAll = scanElementsFunction;//<-- reference, no new function object
});
return toReturn;
}($('.prev, .next')));//this scope preserves the elements returned by jQuery selector
console.log(objectCollection);
});
There's nothing really useful in there, but I tried to incorporate as much tricks and tips while keeping the snippet as short as possible. If some of the code is unclear, let me know and I'll provide some more explanation (which might take an hour or two, 'cause I'm going to grab a bite now :))
The following approach uses the more formal constructor function and prototype which keeps it quite lightweight rather than stacking enclosures. The constructor is...
function Slider(slides, transition) {
// Keeps a reference to the current slide index
this._current = 0;
// An array of slides
this._slides = slides;
// A transition function
this._transition = transition;
}
It accepts an array of slides and a function which will be given two slides when we transition between them. This means we keep control of our transition effect by externalizing it. It is also framework agnostic and has no dependencies to jQuery or any other framework. The constructor itself doesn't do much. The following method is our meat...
// The function that swaps slides
Slider.prototype.goto = function(i) {
// Do a bit of sense checking
if(i > this._slides.length || i < 0)
throw new Error("Slide does not exist");
// Swap the slides by passing them to the transition function
var currentSlide = this._slides[this._current];
var nextSlide = this._slides[i];
this._transition(currentSlide, nextSlide);
// Update the current index
this._current = i;
};
It takes the new index for a slide and passes the old and the new slide to the transition function. It then updates the index it uses for tracking the current slide. We then want to implement a rotating previous and next function so the following methods describe how we can do that using modulus, note we have to add the length of the slides because negative modulus does not work how we want it to for this function.
// Calculate the next index as a rotating index
Slider.prototype.getNextIndex = function() {
return (this._current + 1) % this._slides.length;
};
// Calculate the previous index as a rotating index
Slider.prototype.getPrevIndex = function() {
return (this._current + this._slides.length - 1) % this._slides.length;
};
Then we add some sugar...
// Sugar to go next and prev
Slider.prototype.next = function() {
this.goto(this.getNextIndex());
};
Slider.prototype.prev = function() {
this.goto(this.getPrevIndex());
};
You may have a problem with associating the prev and next buttons with their sliders. You can find them before and after the slider element or as I have done below have them contained in the slider element. To set up sliders using jQuery you could do the following...
$(".slider").each(function() {
var slider = new Slider($(this).find(".slide"), function(a, b) {
$(a).hide();
$(b).show();
});
$(this).data("slider", slider);
$(this).find(".prev").click(function() {
slider.prev();
});
$(this).find(".next").click(function() {
slider.next();
});
});
EDIT Here is it in action http://jsfiddle.net/w8u69/
And because the logic for transitioning is exposed you can quite easily add in transitioning effects without modifying the original Slider "class". http://jsfiddle.net/w8u69/1/
EDIT Just to show the power in this approach, without modifying the original slider "class" you can add in additional logic to automatically move between slides. You can do this with a decorator, or with inheritance, but this example shows how it can be done with composition. http://jsfiddle.net/w8u69/4/
One last thing and this is possibly the most important thing about the pure OO approach, by simply changing the integration code and keeping the OO "classes" untouched, we can reuse the logic we have written for the slider and plug it into a completely different framework. This fiddle shows it working with MooTools http://jsfiddle.net/w8u69/5/

Categories

Resources