I have a while loop:
x = true;
while (x == true) {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?')
}
This shows me each image and then asks if I want to see more on firefox and ie, but in chrome and safari, it only displays the image after I leave the loop. I know this is efficient, but I'm wondering if there's a way to force execution of the line within the loop as I go along?
Thanks for input!
You can add a sequence of setTimeout instead of a loop to force the javascript user thread to stop and thus let the browser refresh the drawing.
var i = 0; // define outside showNextImage to be properly captured by it.
var showNextImage = function() {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?');
if (true) setTimeout(showNextImage, 10);
};
Two answers:
Don't use confirm
If you really want to use confirm, yield to the browser after updating the image but before the confirm
1. Don't use confirm
The best way is to not use confirm at all; it's antiquated and as you've found it behaves slightly differently on different browsers in terms of whether changes to the page are shown.
Instead, I'd use any of the 350,124 "dialog" libraries that are out there (jQuery UI has a nice one, but again, there are a lot of them), which work asynchronously and so you definitely see the page changes. Your loop would become an asynchronous function, but those aren't all that tricky once you're used to them and the benefits are enormous in terms of the user experience.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
ask();
function ask() {
imgElement.src = arr[i];
showDialog(gotAnswer); // the nature of "show dialog" will depend on which one you use
}
function gotAnswer() {
if (userSaidYes) { // Again, depends on the library you're using
completionCallback(i); // Tell the calling code which one they picked
}
else {
// Is there another?
++i;
if (i >= arr.length) {
// No, tell the user
/* left as exercise */
// Tell calling code none was chosen
completionCallback(-1); // Using -1 as a flag for none
}
else {
// Yes, ask about it
ask();
}
}
}
}
2. Use confirm but yield
The issue is that confirm brings things to a screeching halt while the browser asks the user a question. Changes you've made to the page may not show up while the confirm window is active (as you've seen).
If you really want to use confirm, you can still do that, just yield back to the browser briefly first so that it has time to show the page changes. Note, though, that this still may not be a guarantee, if the image takes a long time to download.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
showAndHandOff();
function showAndHandOff() {
imgElement.src = arr[i];
setTimeout(ask, 0);
}
function ask() {
if (confirm('do you want to see more?')) {
++i;
if (i >= arr.length) {
alert("Sorry, there aren't any more.");
completionCallback(-1);
}
else {
showAndHandOff();
}
}
else {
completionCallback(i);
}
}
}
For example:
var x = true,
i = 0,
fn = function() {
document.images['id'].src = arr[i];
x = confirm('do you want to see more?');
if ( x ) {
i = i+1;
setTimeout(fn, 0)
}
};
fn();
Related
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>
I'm trying to make a mobile version of my site, and the jplayer functionality closely resembles that of my main site. The playlist is updated depending on which page you are on, and all the songs except the one you are currently listening to are deleted from the playlist, then the page dynamically adds songs from the page you are viewing.
It works fine on my main page, but my mobile version (which is almost the same code), does not work on.
I set jplayer_playlist.option("removeTime", 0); just like the jplayer documentation suggests, but it doesn't work. Here is a bit of my code so you can see exactly what I'm doing.
function reload()
{
var current = jplayer_playlist.current;
for(var i = 0; i < current; i++)
{
deleteSong(0);
}
var length = theTitles.length;
for(var i = 0; i < (length - 1); i++)
{
deleteSong(1);
}
}
function deleteSong(index)
{
if(!jplayer_playlist.remove(index))
{
console.log("Could not delete it.");
}
}
The first delete does not show the error message, but the second (and every delete after) does. It seems as though it is not recognizing that I set the removeTime to 0, even though I did (and before any delete calls were made). Is there anything else that the jplayer.remove function depends on when you are trying to delete something from it besides removeTime?
I had the same problem and found an answer in jPlaylist docs: http://jplayer.org/latest/demo-02-jPlayerPlaylist/
"Because the second command will only work if the remove animation time, removeTime, is zero."
In my case, I coded the following feature to erase all the songs up to the current one:
When creating the jPlaylist:
var playlistOptions = {
playlistOptions: {
enableRemoveControls: true,
autoPlay: false,
removeTime: 0
},
...
};
playlist = new jPlayerPlaylist(.......);
And the function that erases the songs:
function deleteUpToCurrent(e) {
while(playlist.current != 0) {
playlist.remove(0);
}
return false;
}
Hope it helps!
Cheers
On line 355 of jquery.playlist.js in the jplayerPlaylist.remove function, there is a call to JQuery's remove function:
$(this).remove();
If you look in the source of JQuery, this is delegated to the DOM method
elem.parentNode.removeChild( elem );
In modern browsers, this DOM operation is asynchronous, i.e. it returns immediately but actually removing the DOM element takes time. Therefore the jplayerPlaylist.remove() method returns before the node has actually been removed. Unfortunately, there is no callback. So the only workaround is a setTimeout.
var DELAY = 10;
function reload()
{
var current = jplayer_playlist.current;
var length = jplayer_playlist.playlist.length;
if (current > 0)
deleteSong(0);
else if (length > 1)
deleteSong(1);
if (length > 1)
window.setTimeout(reload, DELAY);
}
You may have to adjust DELAY.
I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks
You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.
Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/
You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}
I have this code that uses an inefficientProcess() that consumes plenty of memory:
My goal is to use some sort of setTimeout(function(){...},0) technique so the browser will not get stuck while executing the code.
How do I change the code so it will work with setTimeout?
function powerOfTwo(num) {
inefficientProcess();
if (num > 0) {
return powerOfTwo(num-1)*2;
} else {
return 1;
}
}
function inefficientProcess() {
var sum;
for (var i=0; i < 500000; i++) {
sum+=10;
}
}
powerOfTwo(1000);
My goal is ofcourse to learn how to avoid browser crush when executing heavy calculations.
Javascript is single-threaded, and all your code is blocking.
There is a new standard in HTML5, WebWorkers API, that will allow you to delegate your task to a different thread. You can then pass a callback function to be executed with the result.
https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers
Simple example:
function powerOfTwo(num, callback) {
var worker = new Worker('inneficient.js');
worker.postMessage('runTask');
worker.onmessage = function(event) {
var num = event.data.result;
var pow;
if (num > 0) {
pow = Multiply(num-1)*2;
} else {
pow = 1;
}
callback(pow);
};
}
powerOfTwo(1000, function(pow){
console.log('the final result is ' + pow);
});
in inneficient.js you have something like:
//inneficient.js
function inefficientProcess() {
var sum;
for (var i=0; i < 500000; i++) {
sum+=10;
}
postMessage({ "result": sum});
}
inefficientProcess();
As was mentioned in Andre's answer, there's a new HTML5 standard that will allow you to set off a task on a different thread. Otherwise, you can call setTimeout with a time of 0 to allow the current execution path to finish (and perhaps some UI changes to render) before the inefficientProcess is called.
But whether you can use HTML5 or not, the powerOfTwo function has to be changed to be asynchronous - whoever calls it needs to provide a callback method that will be called when (a) a thread spun up via WebWorkers returns, or (b) the setTimeout method finishes.
Edited to add example:
function powerOfTwo(num, callback)
{
setTimeout(function ()
{
inefficientProcess();
if (num > 0)
callback(Multiply(num-1)*2);
else
callback(1);
}, 0);
}
function inefficientProcess() { ... }
The HTML element allows you to define when the JavaScript
code in your page should start executing. The “async” and “defer”
attributes were added to WebKit early September. Firefox has been
supporting them quite a while already.
Saw that on this Site
I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/