Im working on a native JavaScript/Ajax based sign-up form. The form uses an ajax function to send the data to a PHP engine which queries the database and what-not.
I'm wondering how to add loading function using JavaScript that allows me to show a loading animation while the PHP is being queried.
Ideally, I'd like to setup some sort of if statement checks how long the query takes and will only return the result after at least a minimum amount of time (like 3 seconds). Otherwise the loading animation will just flash on/off because the data-base query is too quick.
The JS looks like this:
function xmlhttpPost(strURL) {
var xmlHttpReq = false;
var self = this;
// Mozilla/Safari
if (window.XMLHttpRequest) {
self.xmlHttpReq = new XMLHttpRequest();
}
// IE
else if (window.ActiveXObject) {
self.xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
}
self.xmlHttpReq.open('POST', strURL, true);
self.xmlHttpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
self.xmlHttpReq.onreadystatechange = function() {
if (self.xmlHttpReq.readyState == 4) {
updatepage(self.xmlHttpReq.responseText);
}
}
self.xmlHttpReq.send(getquerystring());
}
// send form data
function getquerystring() {
var form = document.getElementById('register-form');
var firstname = document.getElementById('firstname').value;
qstr = 'firstname=' + escape(firstname);
return qstr;
}
// return form data
function updatepage(str){
var result = document.getElementById("result");
result.innerHTML = str;
}
So what I want to do, is while updatepage(str) runs, the loading animation (which can just be gif) is run for a minimum of 3 seconds, then the result is displayed.
What I have at the moment is this:
function updatepage(str){
var result = document.getElementById("result");
// display gif;
setTimeout(function(){
// remove GIF
result.innerHTML = str;
},3000);
}
The issue I see with how I have it setup at the moment is that if DB query takes 5 seconds, the result will actually be displayed in 8 seconds.
Is this the best way of handling this? Or is there a more elegant solution? What's the standard way of doing this kind of thing?
You can do that all in your xmlhttpPost() function:
Set a variable with the current time at the start;
Add / show your image;
In the onreadystatechange compare the current time with the time set at the start, if the difference is 3 seconds or more, remove the image. Otherwise set a timeout for the remaining time and remove the image then.
Related
hey I am trying to figure out how to make my script wait with executing a part of it while an element is loading in, but currently it mostly just passes it, and does not wait for it.
What is the best way to do that in vanilla js?
this is the code
this.heading = document.getElementById("heading");
that i want it to wait or repeat checking until the heading is there for sure every time
Thank you
Do this after you load in your template. Checking repeatedly if something has happened (polling) is usually a bad idea. This should happen because the template loaded. In your case it looks like that would be in goToRoute:
goToRoute: function (htmlName) {
(function(scope) {
var url = 'views/' + htmlName,
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
scope.rootElem.innerHTML = this.responseText;
// Now it's ready
}
};
xhttp.open('GET', url, true);
xhttp.send();
})(this);
}
It might not make sense to put the specific code you're thinking about into goToRoute; to make it more versatile you could think about making goToRoute accept a callback parameter, or using events.
You can repeatedly check for the heading and then do something with it once it is obtained like this.
var heading;
function getHeading() {
var interval = setInterval(function() {
if (heading = document.getElementById('heading')) {
clearInterval(interval);
console.log(heading);
// do some stuff here with your heading
}
}, 100); // 100 represents how often the script checks for existence of heading in ms
}
getHeading();
I am trying to implement a small photo show via a django webserver. Below you can find the javascript code that loads the pictures into the images array and changes the images every x miliseconds. It works if I only load one picture (without the loop) from my django server but it stops working with any kind of loop.
I would love to know why it does not work this way and would be more than happy to receive some other feedback about code improvements. I am not very familiar with ajax calls yet.
Moreover: Django Templates Engine provides a easy way to simplify urls used in the templates. Is there a way to use the {{% url %}} tag inside a .js File as well?
window.images = [];
window.current = 0;
window.imageCount = 2;
function loadImages(){
for(var i = 0; i < window.imageCount; i++){
loadNextImage(i);
}
showImage();
}
function loadNextImage(i) {
// https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest/
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
window.images.push("data:image/jpeg;base64," + xmlhttp.responseText);
}
};
xmlhttp.open('GET', "http://127.0.0.1:8000/mirror/"+i);
xmlhttp.send(null);
}
function showImage() {
if(window.current >= window.imageCount){
window.current = 0;
}
alert("current window count = "+ window.current);
document.getElementById('imgshow').src = window.images[window.current];
window.current = window.current + 1;
setTimeout(showImage, 50000);
}
The direct problem you are encountering is because XMLHttpRequest is Asynchronous and you are dealing with a race condition. Here is what your code now is doing:
Start a loop and tell the browser to que 2 XMLHttpRequests.
Perform the showImage method (even though we have no idea if those 2 AJAX Requests above have returned yet.)
An exception is thrown at this line: document.getElementById('imgshow').src = window.images[window.current]; because window.images is empty.
setTimeout(showImage, 50000); is never executed because of the exception at step 3.
Moving the setTimeout above the document.getElementById('imgshow').src = window.images[window.current]; line might work. However, this is a bad idea.
One solution would be to remove the loop at all, and lazy load the images (only load them once they are needed) as seen below:
window.images = [];
window.current = 0;
window.imageCount = 2;
function loadNextImage(i, callback) {
// https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest/
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
window.images.push("data:image/jpeg;base64," + xmlhttp.responseText);
callback.call();
}
};
xmlhttp.open('GET', "http://127.0.0.1:8000/mirror/"+i);
xmlhttp.send(null);
}
// Increments the image counter and loads the image if needed.
function stepImage() {
// If we have reached the end of the images, restart.
if(window.current >= window.imageCount){
window.current = 0;
}
// Make sure that the image is loaded in the images array,
// if not, load the image, then show it.
if(window.images.length <= window.current) {
loadNextImage(window.current, showImage);
}
// If it's already loaded, just show it.
else showImage();
}
// Displays an image onto the page.
function showImage() {
document.getElementById('imgshow').src = window.images[window.current];
// The counter is not incremented until the image is shown!
window.current++;
}
// Set a timer to render future images.
setInterval(stepImage, 3000);
// Render the first image.
stepImage();
<img id="imgshow" />
This question was posted a couple of days ago, but since I'm a nub it was filled with spaghetti code and that sort of thing (please pardon the form handling as well) That aside, I've added some notes and given some context, but the problem still lies in the second AJAX call.
This is the error that Chrome is throwing "Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource."
I have hidden the URL because it contains an API key that I would rather not share.
Any and all criticisms are warmly welcomed
/*
This module will take a user's name, return an ID
then search more stats in the api with the ID.
*/
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'URL.pre'+player_name+'URL.end';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
}
};
xhr.open('GET', name_url, true);
xhr.send();
// Search stats with ID
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
xhr.done = function(){
stats_url = "URL.pre"+player_id+"URL.end";
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
});
<div id="container">
<img id="duck" src="duck.png" alt="duck">
<div class="form_wrapper">
<h1 id="app_header">*QUACK* What's Your player ID?</h1>
<form>
<input
type="text"
id="player_name"
placeholder="Summoner Name">
<input type="button" id="search" value="Search">
</form>
</div>
<p id="demo"></p>
</div>
<script type="text/javascript" src="script.js"></script>
So your primary error was that if you need to make CORS requests (or any AJAX requests, really), you need to run the code from a server (even localhost).
Google (and most browsers) will freak out at you if your page's protocol is "file:///" and you're trying to load things from the internet (or vice versa). And "file:///" cannot make requests for other files, either.
Future reference: you also can't make "http" requests from an "https" page.
That out of the way, the second issue (the one that was being hidden by CORS security), is that your AJAX requests are being run in parallel right now.
In order to make this work the way you think it should (after the first one returns, run the second one), you would need to:
move all of the code at the bottom, relating to xhr_2 inside of the xhr.onload
move all of the code inside of xhr.done at the bottom inside of the xhr.onload and replace all of the duplicate information (and use the references to the returned results directly)
This results in something like:
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/'+player_name+'?api_key=<THIS IS THE API KEY>';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
// Dropped the XHR_2 stuff here
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
stats_url = "https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/"+player_id+"/summary?season=SEASON2016&api_key=<THIS IS THE API KEY>";
// CHANGED THIS TO BE XHR_2.onload -- IN HERE I KNOW XHR_1 IS ALREADY FINISHED
xhr_2.onload = function(){
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
}
};
xhr.open('GET', name_url, true);
xhr.send();
});
That should solve practically all of your woes.
The point of this is that onload is a callback which gets fired long after the program has been run, but xhr_2 was firing immediately after you requested data for xhr_1 (not after it was returning the data).
As such, player_id was undefined.
We want to wait until after we know we have player_id, and we know we have it (or some error) when we're inside the callback to xhr_1.onload.
This gets terribly confusing and very nested, and while I think that Promises and Async Functions / Generators are brilliant solutions for managing that complexity, that's way beyond the scope of this; so instead, I'd suggest looking at some functional composition, to simplify all of this:
function noop () { } // do nothing
function getJSON (url, onload, onerror) {
var xhr = new XMLHttpRequest();
onload = onload || noop; // what I've been given or nothing
onerror = onerror || noop; // " "
xhr.onload = function () {
var data;
var error;
try {
// it's possible for parse to throw on malformed JSON
data = JSON.parse(xhr.responseText);
} catch (e) {
error = e;
}
return error ? onerror(error) : onload(data); // fire one or the other (don't fall into the handler, if onload throws)
};
xhr.onerror = onerror;
xhr.open("GET", url);
xhr.send();
}
// localize URL construction
function buildPlayerIdUrl (name) { return "https://______" + name + "_____"; }
function buildPlayerStatsUrl (id) { return "https://______" + id + "_____"; }
// gets player by name and runs a function after the player has been loaded
function getPlayer (player_name, done, error) {
var id_url = buildPlayerIdUrl(player_name);
function buildPlayer (response) {
var player = response[player_name];
return player;
}
function onload (response) {
done(buildPlayer(response));
}
// Load the JSON, build the player, pass the player to done()
getJSON(url, onload, error);
}
// get stats by player id and runs a function after the stats have been loaded
function getPlayerStats (player_id, done, error) {
var stats_url = buildPlayerStatsUrl(player_id);
function buildStats (response) {
var summary = response.playerStatsSummaries;
return summary;
}
function onload (response) {
done(buildStats(response));
}
// Load the JSON, build the stats, pass the stats to done()
getJSON(stats_url, onload, error);
}
// perform a search by player name
// note: All changes in step-number (1, 2, 3) are asynchronous,
// and thus, must be nested in callbacks of some sort
function search (player_name) {
// Step 1: load the player
getPlayer(playerName, function (player) {
// Step 2a: update the DOM with the player name/id
updatePlayerDom(player);
// Step 2b: load the player stats
getPlayerStats(player.id, function (stats) {
// Step 3: update the DOM with the stats
updateStatsDom(stats);
});
});
}
// player DOM update; keeping it nice and simple
function updatePlayerDom (player) {
document.querySelector(".Player-id").textContent = player.id;
document.querySelector(".Player-name").textContent = player.name;
}
// stats DOM update; same as above
function updateStatsDom (stats) {
document.querySelector(".Player-stats").textContent = stats.playerStatType;
}
// bootstrap yourself to your UI
some_button.onclick = function () {
var player_name = some_input.value;
search(player_name); // kick the whole thing off
};
It's definitely more code, but it's also simpler to make edits to each individual piece, without stepping on the toes of other pieces.
It's (hopefully) also easier to see the _eventual timeline_ of all of the pieces, and how they flow, inside of the search( ) itself.
I have some issues with a for-loop and AJAX. I need to fetch some information from a database, so I pass the incrementing variable to PHP to grab the information and then send it back. The trouble is that it skips immediately to the maximum value, making it impossible to store any of the information.
I would prefer not to use jQuery. It may be more powerful, but I find Javascript easier to understand.
Here is the JS code:
for (var i = 0; i <= 3; i++) {
var js_var = i;
document.getElementById("link").onclick = function () {
// ajax start
var xhr;
if (window.XMLHttpRequest) xhr = new XMLHttpRequest(); // all browsers
else xhr = new ActiveXObject("Microsoft.XMLHTTP"); // for IE
var url = 'process.php?js_var=' + js_var;
xhr.open('GET', url, false);
xhr.onreadystatechange = function () {
if (xhr.readyState===4 && xhr.status===200) {
var div = document.getElementById('test1');
div.innerHTML = xhr.responseText;
if (js_var == 2) {
var rawr = document.getElementById('test2');
rawr.innerHTML = xhr.responseText;
}
}
}
xhr.send();
// ajax stop
return false;
}
};
Here is the PHP code:
<?php
if (isset($_GET['js_var'])) $count = $_GET['js_var'];
else $count = "<br />js_var is not set!";
$con = mysql_connect("xxx","xxxxx","xxxx");
mysql_select_db('computerparty_d', $con);
$get_hs = mysql_query("SELECT * FROM hearthstone");
$spiller_navn = utf8_encode(mysql_result($get_hs,$count,1));
echo "$spiller_navn";
?>
what you actually are doing is binding an onclick event in your for-loop not sending ajax request, and the other point is, it immediately overrides the previous onclick handler which you have created in the previous iteration.
So if you want to add multiple listeners you should first consider using nested functions and closures to keep the i variable safe for each listener, and then use addEventListener instead of setting the onclick function. Considering these points you can do this instead:
for (var i = 0; i <= 3; i++) {
var clickFunc = (function (js_var) {
return function () {
// ajax start
var xhr;
if (window.XMLHttpRequest) xhr = new XMLHttpRequest(); // all browsers
else xhr = new ActiveXObject("Microsoft.XMLHTTP"); // for IE
var url = 'process.php?js_var=' + js_var;
xhr.open('GET', url, false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var div = document.getElementById('test1');
div.innerHTML = xhr.responseText;
if (js_var == 2) {
var rawr = document.getElementById('test2');
rawr.innerHTML = xhr.responseText;
}
}
}
xhr.send();
// ajax stop
return false;
};
})(i);
document.getElementById("link").addEventListener("click", clickFunc);
}
Be aware that you're making an synchronous AJAX call, which is undesirable (it hangs the browser during the request, which might not end). You may have problems in some browsers with this because you're calling onreadystatechange, that shouldn't be used with synchronous requests.
It looks like you are making the AJAX request with a user click.
for (var i = 0; i <= 3; i++) {
var js_var = i;
document.getElementById("link").onclick
When this JS is executed it will override the "onclick" listener of "link" twice. First time it is assigned for the first time, second time it is overwritten, and the third time it is overwritten again. The result is that when the "link" element is clicked only the last listener exists, resulting in making a single AJAX request for the last configuration.
HTTP request are expensive(time), it might be worth to get all of the data in one request and then use client-side JS to sift through that data accordingly.
jQuery is not more powerful than JS, it is JS with a bunch of wrapper functions. My personal opinion is that once IE9 is no longer relevant, jQuery will be only used by people who know jQuery and not JS.
I've searched SO for similar issues (e.x. Chrome does not redraw <div> after it is hidden and Force DOM redraw/refresh on Chrome/Mac ) but none of questions gave me the solution to my problem. I am writing modem configuration panel, the webpage with ,,tabs''. On every tab there are some settings-just like configuration panel of any router.
Saving configuration (done when user clicks on Save button) takes few seconds (my embedded platform is not a speed king), so I decided to put special PLEASE WAIT window (div to be precise) which is usually hidden, but is shown when needed to calm user down :-).
Everything works fine on Firefox: after clicking save, the PLEASE WAIT div shows and then the configuration is saved using POST method. However, on Chrome 26 and Chromium 25 the div does not show until the configuration is saved. As you can see in SaveConfiguration function after executing PHP script that saves configuration the alert is shown-this is where the PLEASE WAIT div shows up on Chrome. It looks like Chrome is not redrawing page but immediately starts launching POST script. Has anyone had similar issues and now how to fix this problem?
Below are fragments of my code, I have only supplied functions that might give a clue what I'm doing. I can post more code if that helps.
function showLoadingScreen(yes)
{
if(yes)
{
document.getElementById("loadingtext").innerHTML="Please wait...";
document.getElementById("loading_overlay").style.display="block";
document.getElementById("loading_window").style.display="block";
}
else
{
document.getElementById("loading_overlay").style.display="none";
document.getElementById("loading_window").style.display="none";
}
}
function postDataSync(url, params)
{
var XMLHttpRequestObject = false;
if (window.XMLHttpRequest)
{
XMLHttpRequestObject = new XMLHttpRequest();
} else
if (window.ActiveXObject)
{
XMLHttpRequestObject = new
ActiveXObject("Microsoft.XMLHttp");
}
if(XMLHttpRequestObject)
{
XMLHttpRequestObject.open("POST", url, false);
XMLHttpRequestObject.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
XMLHttpRequestObject.send(params);
{
if (XMLHttpRequestObject.readyState == 4 &&
XMLHttpRequestObject.status == 200)
{
var result = XMLHttpRequestObject.responseText;
delete XMLHttpRequestObject;
XMLHttpRequestObject = null;
return result;
}
}
}
return '';
}
function SaveConfiguration()
{
var errors=checkForm();
if(errors!="")
{
printError("Can't save configuration because there are errors in current tab:<br><br>"+errors);
return;
}
showLoadingScreen(true);
saveTab();
var retval=postDataSync('actions/saveconf3.php','');
alert("Settings saved. The modem is now being reconfigured.");
document.location = "http://" + retval;
}
You are using ajax synchronously rather than asynchronously meaning javascript execution halts during the request. To fix make the following change:
XMLHttpRequestObject.open("POST", url, true);
You need to use a callback for the behaviour after the request is complete. Something like this:
function postDataSync(url, params, success)
{
var XMLHttpRequestObject = false;
if (window.XMLHttpRequest)
{
XMLHttpRequestObject = new XMLHttpRequest();
} else
if (window.ActiveXObject)
{
XMLHttpRequestObject = new
ActiveXObject("Microsoft.XMLHttp");
}
if(XMLHttpRequestObject)
{
XMLHttpRequestObject.open("POST", url, true);
XMLHttpRequestObject.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
XMLHttpRequestObject.send(params);
XMLHttpRequestObject.onreadystatechange = function() {
if (XMLHttpRequestObject.readyState == 4 &&
XMLHttpRequestObject.status == 200)
{
var result = XMLHttpRequestObject.responseText;
delete XMLHttpRequestObject;
XMLHttpRequestObject = null;
if (typeof success === 'function') success(result);
}
}
}
return '';
}
function SaveConfiguration()
{
var errors=checkForm();
if(errors!="")
{
printError("Can't save configuration because there are errors in current tab:<br><br>"+errors);
return;
}
showLoadingScreen(true);
saveTab();
postDataSync('actions/saveconf3.php','', saveComplete);
}
function saveComplete(result) {
showLoadingScreen(false);
alert("Settings saved. The modem is now being reconfigured.");
document.location = "http://" + result;
}
If you have heavy synchronous code (in practice, operations on hundreds or thousands of objects that are already in memory, or calculating pi to a gazillion digits) you can use setTimeout to give the browser time to catch up with any rendering tasks. You'd either need to call setTimeout for each task, or if you have a long-running task, split it up in batches first. This requires quite a bit of refactoring though, since every task needs to be represented as a function that can be passed to setTimeout.
I wouldn't use XMLHTTPRequest synchronously ever.
If setTimeout(fn, 0) does not trigger the "incremental" rendering, try a higher value, until it works. I think I needed to use a value of 100ms between jobs in some cases, for some browsers (I don't recall which).
You may need to yield to the browser even quicker if you want to achieve 60fps, or 30fps. Then you need to stay under 16ms or 33ms for each task. That gets very tight on slow hardware, such as (older types of) smartphones. Then, instead of setTimeout, you can best use requestAnimationFrame, if available.