How to update window after each iteration of a loop in js? - javascript

I'm trying to update the window after each iteration of the loop. The code in question is below. After picking the initial live cells I press next which should iterate through the loop. It reaches the correct final state, but does not display the states in-between.
Plnkr link: https://plnkr.co/edit/Gtp87m48YT5wGbSytQX4?p=preview
I understand that I probably have to use setInterval, but whenever I try implementing it, it just freezes the window or skips to the end. I've tried using the angular $interval, but I have the same problem
$scope.next = function newgrid() {
var num=3;
while(num>0)
{
$scope.history.push($scope.board);
$scope.board = computeNext($scope.board);
num--;
}
};
Edit updated code. Now it just stays stuck on the initial state:
$scope.next = function newgrid() {
$interval(function() {
$scope.history.push($scope.board);
$scope.board = computeNext($scope.board);
}, 1000);
};

Related

javascript, user clicks too fast

i'm new to javascript, and i'm trying to build some kind of memory game.
the game works fine until the user clicks too fast on the cards and more than 2 cards are "open".
the function is activated by clicking. i tried to check if the function is already active by adding a global var, set it to 1 ( function busy) at entrance and set it back to 0 (free) at the end. it didn't work.
any ideas how to solve it?
code is:
var isProcessed =0;
function cardClicked(elCard){
//check to see if another click is being processed
if(isProcessed===1){
return;
}
//if function is not already active - set it to "active" and continue
isProcessed=1;
//doing all kind of stuff
//setting function to "free" again
isProcessed=0;
}
I believe the problem with your code is that when you call the function it both processes and frees the card currently being clicked which allows other cards to be clicked as well.
A simple way to fix it is: (I'm assuming after two cards are clicked it will "close" and others will be available)
var isProcessed =0;
var selectedPair=[];
function cardClicked(elCard){
//add to the amount of cards processed
isProcessed++;
//If there are two cards "processed" then:
if(isProcessed===2){
//reset the amount processed after two cards have been opened
isProcessed=0;
//"close" card functionality
//clear the array of selected cards;
selectedPair=[];
return;
}else{
//add card to the selectedPair array so we can keep track
//which two cards to "close" after it resets
selectedPair.push(elCard);
//do all kinds of stuff
}
}
Your plan should work. As #JustLearning mentioned in the comment, it might be better to to disable the button instead of using a flag variable. This will offer visual clues to the user that they can't click.
Having said that, the important thing is that resetting your flag, or enabling he button, has to happen after //doing all kind of stuff is done.
Assuming that //doing all kind of stuff is something slow and asynchronous this means resetting it in the callback or when a promise resolves.
Here's a quick example that asynchronously runs a count. During the count isProcessing is set to true. In the callback function — not after it — it resets the flag.
function someSlowThing(cb){
let n = 30
let i = 0
let d = document.getElementById('count')
let itv = setInterval(() => {
if (i > n) {
clearInterval(itv);
i = 0
return cb()
}
d.innerHTML = i++
}, 50)
}
var isProcessing = false;
function process(e){
if(isProcessing) {
console.log("wait please")
return
}
isProcessing = true
someSlowThing(() => {
isProcessing = false // <-- important that the happens in the callback
console.log('done')
})
// don't try to set isProcessing here, it will happen too soon.
}
<div id = 'count'>0</div>
<button onclick='process()'>start</button>

How do I create and focus a new element in AngularJS?

I have a form with a dynamic number of inputs, controlled by AngularJS.
<body ng-app="mainApp" ng-controller="CreatePollController" ng-init="init(3)">
<form id="createPollForm">
<input class="create-input" ng-repeat="n in questions" id="q_{{$index}}" name="q_{{$index}}" type="text" ng-keypress="createInputKeypress($event);"/>
Add Question
</form>
</body>
This is being controlled by the following angular code:
app.controller('CreatePollController', function($scope) {
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for angular update ????
var nextId = "#q_" + (++idx);
$(nextId).focus();
}
};
});
Currently, when the user hits the Enter key while focused on a text input, the createInputKeypress function is called and the browser focuses the next input in the form. However, if you are currently focused on the last element in the form, it adds a new question to the questions array, which will cause another input to be generated in the DOM.
However, when this new element is created, the focus() call isn't working. I suspect this is because angular doesn't add the new element right away, so trying to use jQuery to locate and focus the new element isn't working.
Is there a way to wait for the DOM to be updated, and THEN focus the new element?
As you might already know, javascript is turn based, that means that browsers will execute JS code in turns (cycles). Currently the way to prepare a callback in the next javascript cycle is by setting a callback with the code we want to run on that next cycle in a timeout, we can do that by calling setTimeout with an interval of 0 miliseconds, that will force the given callback to be called in the next javascript turn, after the browser finishes (gets free from) the current one.
Trying to keep it simple, one browser cycle executes these actions in the given order:
Scripting (where JS turn happen)
Rendering (HTML and DOM renderization)
Painting (Painting the rendered DOM in the window)
Other (internal browser's stuff)
Take a look at this example:
console.log(1);
console.log(2);
setTimeout(function () {
console.log(3);
console.log(4);
}, 0);
console.log(5);
console.log(6);
/** prints in the console
* 1 - in the current JS turn
* 2 - in the current JS turn
* 5 - in the current JS turn
* 6 - in the current JS turn
* 3 - in the next JS turn
* 4 - in the next JS turn
**/
3 and 4 are printed after 5 and 6, even knowing that there is no interval
(0) in the setTimeout, because setTimeout basically prepares the given callback to be called only after the current javascript turn finishes. If in the next turn, the difference between the current time and the time the callback was binded with the setTimeout instruction is lower than the time interval, passed in the setTimeout, the callback will not be called and it will wait for the next turn, the process repeats until the time interval is lower than that difference, only then the callback is called!
Since AngularJS is a framework wrapping all our code, angular updates generally occur after our code execution, in the end of each javascript turn, that means that angular changes to the HTML will only occur after the current javascript turn finishes.
AngularJS also has a timeout service built in, it's called $timeout, the difference between the native setTimeout and angular's $timeout service is that the last is a service function, that happens to call the native setTimeout with an angular's internal callback, this callback in its turn, is responsible to execute the callback we passed in $timeout and then ensure that any changes we made in the $scope will be reflected elsewhere! However, since in our case we don't actually want to update the $scope, we don't need to use this service, a simple setTimeout happens to be more efficient!
Knowing all this information, we can use a setTimeout to solve our problem. like this:
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for the next javascript turn
setTimeout(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
}, 0);
}
};
To make it more semantic, we can wrap the setTimeout logic
in a function with a more contextualized name, like runAfterRender:
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
Now we can use this function to prepare code execution in the next javascript turn:
app.controller('CreatePollController', function($scope) {
// functions
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
// $scope
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
runAfterRender(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
});
}
};
});

$timeout use inside AngularJS factory

I used AngularJS factory to create new instance model for my project, and each model contain a progress value that will be incremented, paused, and set back to 0 based on "start", "pause", and "stop" user actions.
app.factory('ModelA', ['$timeout', function($timeout) {
function ModelA(progress) {
this.progress = progress;
};
ModelA.prototype = {
startProgress: function() {
this.counterFunction();
},
counterFunction: function() {
this.progress++;
if(this.progress == 100) {
this.progress = 0;
}
//console.log(this.progress);
//console.log(this.counterFunction);
progressTimeout = $timeout(this.counterFunction, 1000);
},
// Haven't tested the method below
pauseProgress: function() {
$timeout.cancel(progressTimeout);
},
stopProgress: function() {
$timeout.cancel(progressTimeout);
this.progress = 0;
}
};
return ModelA;
}]);
For some reason, when I call startProgress() in the ng-click expression function, the progress will increment 1 and then stop. I added logs to check this.counterFunction for every call. I realized it only prints out 1 and the whole counterFunction on the first time. As for the second time, this.progress will be NaN and the counterFunction will show undefined.
I'm new to AngularJS, could someone please help me out? Thanks.
The this object in the function called by the $timeout, i.e. this.counterFunciton is not the ModelA instance, therefore you should use
$timeout(this.counterFunction.bind(this), 1000) instead.
You can read up this article about binding this object in JavaScript.
A working codepen for your reference.
The execution context this changes when the $timeout gets executed. You would need to preserve the ModelA this in $timeout(this.counterFunction.bind(this), 1000). You bind and pass the this to this.counterFunction and thus counterFunction has the right access to this.progress.
Check here more info about the this problem here. $timeout is the wrapper for window.setTimeout
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#The_this_problem

Why is my interval clearing only halfway?

I have been working on my first major programming project and so far it has been going pretty well. My goal is to have an animation run until either an event is triggered or 15 seconds have passed. After the animation times out I want to animation to repeat. My current solution to the plan is shown below.
var animationSpeed = 40;
var animationTimout = 375;
var testTime = 1;
//some more set up variables
function onClick() {
startDrive();
setTimeout(startDrive, 15000); //****
}
function startDrive() {
var Driving = setInterval(carDriving, aniSpeed);
}
function carDriving() {
testLocation();
drawCar();
angleCalc();
newLocation();
getInfo();
}
function testLocation() {
//this code gets information on whether or not the animation should be stopped
testTime = testTime + 1
if(a === 1 || testTime > animationTimeout) {
//a test to cancel the animation, the other variables test to
clearInterval(Driving);
}
}
function drawCar() {
//draws the car
}
function angleCalc() {
//gets some info on how to move the car
}
function newLocation() {
//decides on new coords for the car based on angleCalc();
}
function getInfo() {
//gets some info I plan to use later in the project
}
When I run the code without the starred line, the whole thing works. The car animates as I want it to and stops if the conditions for stopping are met. The car freezes where it was on the canvas, and it seems as if the interval was cleared. When I add the starred line of code, the animation seems to work, but it runs twice as fast as before. I am utterly lost and nothing I try works. Please help.
The problem is likely due to the local variable defined here:
function startDrive() {
var Driving = setInterval(carDriving, aniSpeed);
}
The variable Driving is only defined in the function startDrive, it's a local variable because you are using var to define it inside the function. So when you attempt to access it inside testLocation() you are not accessing the same variable. In fact when you do clearInterval(Driving) the variable Driving isn't defined. A simple solution would be to make Driving global by removing the var:
function startDrive() {
Driving = setInterval(carDriving, aniSpeed);
}
Or you can pass the timer as a parameter inside the testLocation function. This way you will be clearing the interval properly.

Worn out getting animation to sequence

This is originally from (Pause execution in while loop locks browser (updated with fiddles))
I have been at this all day and I can't figure out how to keep javascript from advancing to the next line and in essence executing all lines at once. I have tried every combination of delay / setTimeout I can think of to no avail.
I just want the elements in the array to flash once then pause, then do it again for another element in the array till all elements have been removed and the array is empty.
But because javascript is executing all lines at once I end up with the appearance of all elements flashing at the same time.
Here is the fiddle:
http://jsfiddle.net/ramjet/xgz52/7/
and the relevant code:
FlashElement: function () {
while (elementArray.length) {
alert('a ' + elementArray.length);
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
alert('delay complete');
elementArray = elementArray.not($el);
alert('array popped');
alert('z ' + elementArray.length);
}
},
ANSWER FOR THIS SITUATION. Hopefully it will help others.
As Zach Saucier points out the loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code. As Zach pointed out and I read here (http://javascript.info/tutorial/settimeout-setinterval) while he was responding setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
The reason why you were having trouble is because setTimeout function is non-blocking and will return immediately. Therefore the loop will iterate very quickly, initiating each of the timeouts within milliseconds of each other instead of including the previous one's delay
As a result, you need to create a custom function that will wait on the setInterval to finish before running again
FlashElement: function () { // Call it where you had the function originally
myLoop();
},
...
function myLoop() {
setTimeout(function () { // call a setTimeout when the loop is called
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
if (0 < elementArray.length) { // if the counter < length, call the loop function
myLoop();
}
}, 1000)
}
Feel free to change the delay to whatever value you wish (3000ms to let each fade finish before the last at the moment). If you want to start the fade in of the next before the previous ends and keep them in their original positions you would have to animate the opacity using .css instead of using fadeIn and fadeOut
My answer is based on this answer from another SO question

Categories