How to force javascript to execute sequentially - javascript

I have a page where the user can click a button to retrieve data via an xhr get request. While the data is loading and being parsed, I want a loading message to be displayed, which will be replaced with the data once it is ready. I'm using dojo libraries, so would rather not include jQuery or other libraries.
This is a simplified version of the set up I'm using:
HTML
<div id = "clickMe"> Click Me! </div>
<div id = "results" class = "hidden">
Please wait while we retrieve the results
</div>
CSS
.hidden {display: none;}
Javascript
// Bind function to click me div
var clickMe = document.getElementById('clickMe');
clickMe.addEventListener('click', getResults, false);
function getResults () {
// Display the loading message while results are retrieved
var resultsDiv = document.getElementById('results');
resultsDiv.classList.remove('hidden');
// Get the data and parse it here using a standard dojo.xhrGet method
var displayResults = getData();
// Output data to resultsDiv, overwriting loading message
resultsDiv.innerHTML = displayResults;
}
The problem I'm having is that the getResults function always waits until the getData function has completed before removing the 'hidden' class and showing the results div. This means that the user never sees the loading message, only the retrieved data, even if there's a delay while the data is processed. However, if I put an alert in the middle the function is forced to pause, the loading message is displayed:
function getResults () {
// Display the loading message while results are retrieved
var resultsDiv = document.getElementById('results');
resultsDiv.classList.remove('hidden');
// The loading message will be displayed if this alert is included
alert ("Hello world!");
// Get the data and parse it here using a standard dojo.xhrGet method
var displayResults = getData();
// Output data to resultsDiv, overwriting loading message
resultsDiv.innerHTML = displayResults;
}
I have tried replacing the alert with console.log, but it reverts to not showing the loading message. I've also tried setting up getting the data as a callback function inside displaying the loading message, but again it doesn't show anything. I have also tried with the get request set to sync: true as well as sync: false, but again no luck.
How can I make sure the loading message is displayed while waiting for getData?
Edit:
This is the getData function. I have tried both with and without syncing.
function getData() {
var targetUrl = //some url;
var restResponse;
dojo.xhrGet({
url: targetUrl,
sync: true; // no difference when this is omitted
load: function(result) {
restResponse = result;
}
});
// Parse the rest response - fairly long function so I won't paste it here
var parsedResponse = parseResult(restResponse);
return parsedResponse;
}

My recommendation is to learn how to write asynchronous code and dojo/Deferred.
Instead of getData, rename the method to loadData and
loadData: function() {
return xhr('', {...}); // this returns a deferred
}
function getResults () {
var resultsDiv = dom.byId('results');
domClass.remove(resultsDiv, 'hidden');
loadData().then(function(displayResults) {
resultsDiv.innerHTML = displayResults;
});
}
http://dojotoolkit.org/reference-guide/1.9/dojo/Deferred.html

You can use deffereds and promises in jQuery
http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html. If you work with ajax request you can chain like this (since jQuery 1.8).
var promise1 = $.ajax("/myServerScript1");
function getStuff() {
return $.ajax("/myServerScript2");
}
promise1.then(getStuff).then(function(myServerScript2Data){
// Both promises are resolved
});

Related

How to control the visibility to Cesium/CZML DataSources

I have a CZML datasource declared:
public geometryDataPromise: Cesium.CzmlDataSource;
I am loading it with an endpoint above when a component is loaded:
if(!this.store.geometryDataPromise) {
this.store.geometryDataPromise = Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`);
}
all the rendered objects are displayed to terrain but trying to follow the instructions by doing:
this.store.geometryDataPromise.show = false;
the objects are not being hidden
The problem here is that Cesium.CzmlDataSource.load does not return a Cesium.CzmlDataSource. It returns a Promise to asynchronously go get a CzmlDataSource, and that's not the same thing at all. Your code is trying to show or hide the promise, that's not a thing that gets displayed.
var dataSourcePromise = Cesium.CzmlDataSource.load( ... );
var dataSource = null;
dataSourcePromise.then(function(d) { dataSource = d; });
Note that after the above code runs, dataSource will be null for some time while the browser waits for the server's response to finish downloading. Once the callback function fires, the dataSource is ready.
function onClick() {
if (dataSource !== null) {
dataSource.show = !dataSource.show;
}
}
You can wire up a click handler for a toggle button like this. But the toggle won't do anything until after the dataSource is downloaded and ready.
First I have to take the result of the Cesium.CzmlDataSource.load promise
Cesium.when(Cesium.CzmlDataSource.load(environment.apiBaseURL + `/protectedareas/geometry/all`), result => {
this.sources = result;
this.viewer.dataSources.add(this.sources);
});
and then just change it's fiend show when the visibility changed
this.store.sourceVisibility.subscribe(visibility=>this.sources.show=visibility);

Reusing ajax call results on jQuery click events

The task is to fetch some data from pokemon api and append it to the list. Api request results have links on previous and next pages.
HTML:
<ul class="poke-list"></ul>
<div class="pagination">
<button class="prev">Prev</button>
<button class="next">Next</button>
</div>
here's a function that makes an api call (async await is necessary):
var getData = async function (url, pokemonName) {
var response;
if(!pokemonName) {
response = await $.get(url);
} else {
response = await $.get(url + pokemonName);
}
return response;
};
A function to append content and handle click events:
var appendContent = function (data) {
var list = $('.poke-list');
list.empty();
var res = data;
var pokemons = res.results;
pokemons.forEach(function (item) {
list.append($(`<li>${item.name}</li>`));
$('.prev').on('click', function (event) {
res = data;
var url2 = res.previous;
if (url2 === undefined || url2 === null) {
alert('Limit reached!');
} else {
getData(url2)
.then(appendContent);
}
});
$('.next').on('click', function (event) {
res = data;
var url = res.next;
if (url === undefined || url ===null) {
alert('Limit reached!');
} else {
getData(url)
.then(appendContent)
});
});
I call it on page load (yes it is necessary):
$(function {
getData()
.then(appendcontent)
here is a fiddle: https://jsfiddle.net/aikast/4rgcvd7z/
What happens is that every time append function is called it creates new click events (I know that's how it should work), and the code stops working properly (it does not see current ajax call results, so every value is null).
Stopping event propagation did not help.
Is there a way to organise it differently? I can't find a way for click event handlers to see ajax call results outside of appendContent function scope
The question was marked duplicate, but buttons are static, they are not dynamically added!
you should try something like below.
now only once click event fired.
$(next).unbind( "click" );
so every time when your function call, first it will unbind previous event.
you can check live demo as well.
https://jsfiddle.net/zhna7ksu/
.unbind() is deprecated and you should use the .off() method instead. Simply call .off() right before you call .on()
$(next).off().on("click", .......);
store data/res (why double naming the same variable?) somewhere outside appendData (window.pokemonResponse for example) function context and declare previous and next logic outside appendData. Also, use let instead of var, even IE 11 supports let

.each() loop wait for another loop to finish before starting

I have two main loops for posts and comments. However the comments don't display, presumably because the post ID is not on the DOM yet (?).
$.getJSON('fresh_posts.php',function(data){
//posts
$.each(data.freshposts, function(id, post) {
// set variables and append divs to document
var id = post.id;
...
});
// comments attached to each post
$.each(data.freshcomments, function(id, commentList) {
$.each(commentList, function(index, c) {
// set variables and append comments to each post div
var postid = c.postid; // this is the same as post.id (linked)
...
var full = "<div> ... </div>";
$('#comment-block'+postid).append(full); // comment-block+postid is attached with each post div, so it tells the comment which div it should be appended to.
})
});
});
Does not display comments ^
If I wrap the $.each loop for the comments in a setTimeOut(function(){},1), the comments are able to be displayed - I suppose it needs to wait 1 millisecond before the loop can commence? However this doesn't seem like a good/fool-proof way to ensure this.
setTimeOut(function(){
$.each(data.freshcomments, function(id, commentList) {
...
})
},1)
Displays comments ^
I managed to solve this after a few hours of work.
I made two functions, getPosts() and appendComments(). I used the promise method:
function getPosts(){
var deferred = new $.Deferred(); // new deferred
$.getJSON('fresh_posts.php',function(data){
global_save_json = data.freshcomments;
var howManyPosts = Object.keys(data.freshposts).length; // how many posts there are (object length)
var arrayCount = 0;
$.each(data.freshposts, function(id, post) {
// closing tags for above
return deferred.promise();
}
I had an async method (just learned what that was) to check if the image was designed for retina displays within getPosts.
if (retina === "true") {
var img = new Image();
img.onload = function() {
var retina_width = this.width/2;
var post = '...';
$('.main').append(post);
arrayCount++; // for each post add +1 to arrayCount
if (arrayCount == howManyPosts) { // if last post
deferred.resolve();
}
}
img.src = image;
} else {
...
I then did
getPosts().then(appendComments);
after closing tag of the function getPosts. So basically even though the function finishes, it still waits for the async method to also finish within that function before the function getComments is called. That way the post id's will exist on the document for the comments to be appended to.
appendComments looks like this:
function appendComments(){
if (global_save_json != null){
$.each(global_save_json, function(id, commentList) {
$.each(commentList, function(index, c) {

FirefoxOS: return array using device storage api

I am just getting started with coding for FirefoxOS and am trying to get a list of files in a directory.
The idea is to find the name of each file and add it to the array (which works), but I want to return the populated array and this is where I come unstuck. It seems that the array gets populated during the function (as I can get it to spit out file names from it) but when I want to return it to another function it appears to be empty?
Here is the function in question:
function getImageFromDevice (){
var imageHolder = new Array();
var pics = navigator.getDeviceStorage('pictures');
// Let's browse all the images available
var cursor = pics.enumerate();
var imageList = new Array();
var count = 0;
cursor.onsuccess = function () {
var file = this.result;
console.log("File found: " + file.name);
count = count +1;
// Once we found a file we check if there are other results
if (!this.done) {
imageHolder[count] = file.name;
// Then we move to the next result, which call the cursor
// success with the next file as result.
this.continue();
}
console.log("file in array: "+ imageHolder[count]);
// this shows the filename
}
cursor.onerror = function () {
console.warn("No file found: " + this.error);
}
return imageHolder;
}
Thanks for your help!
Enumerating over pictures is an asynchronous call. Essentially what is happening in your code is this:
You are initiating an empty array
You are are telling firefox os to look for pictures on the device
Then in cursor.onsuccess you are telling firefox os to append to the array you have created WHEN it gets back the file. The important thing here is that this does not happen right away, it happens at some point in the future.
Then you are returning the empty array you have created. It's empty because the onsuccess function hasn't actually happened.
After some point in time the onsuccess function will be called. One way to wait until the array is full populated would be to add in a check after:
if (!this.done) {
imageHolder[count] = file.name;
this.continue();
}
else {
//do something with the fully populated array
}
But then of course your code has to go inside the getImageFromDevice function. You can also pass a callback function into the getImageFromDevice function.
See Getting a better understanding of callback functions in JavaScript
The problem is with the aSynchronous nature of the calls you are using.
You are returning (and probably using) the value of imageHolder when it's still empty - as calls to the "onsuccess" function are deferred calls, they happen later in time, whereas your function returns immediately, with the (yet empty) imageHolder value.
You should be doing in this case something along those lines:
function getImageFromDevice (callback){
...
cursor.onsuccess = function () {
...
if (!this.done) {
// next picture
imageHolder[count] = file.name;
this.continue();
} else {
// no more pictures, return with the results
console.log("operation finished:");
callback(imageHolder);
}
}
}
Or use Promises in your code to accomplish the same.
Use the above by e.g.:
getImageFromDevice(function(result) {
console.log(result.length+" pictures found!");
});

javascripts works when I step through doesnt if I dont

Has anyone found that their javascript doesnt work, but when they step through the code it works fine ?
var cookie = getCookie('lusr');
var username = cookie;
if(!cookie){
$("#UserNav").load("loginform.html");
$("#loginbtn").click( function(){
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username,password);
});
}else{
$("#UserNav").load("user.htm");
$("#WelcomeUser").text("Welcome "+ username);
}
My issue occurs on this line :
$("#WelcomeUser").text("Welcome "+ username);
That's because load() is asynchronous: it returns right away, performs its work in the background, then calls a user-provided function when its task is complete. The stepping delay gives you the illusion that the function is synchronous and performs all its work before returning.
Therefore, you should pass a callback function to load() and perform your subsequent work inside that callback:
var cookie = getCookie("lusr");
if(!cookie) {
$("#UserNav").load("loginform.html", function() {
$("#loginbtn").click(function() {
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username, password);
});
});
} else {
$("#UserNav").load("user.htm", function() {
$("#WelcomeUser").text("Welcome " + cookie);
});
}
You are using the load() function which asynchronously fetches from the server. This means your form has not loaded by the time you go searching for its fields.
The reason it works when you step through is because it gives it time to load the form while you step.
You can use another version of load which has an asynchonous callback function, allowing you to provide functionality only to be called once the load is complete.
Check the jQuery docs for more info.

Categories