I'm trying to set up a script to get total page size, including images. I'm having serious problems with ajax asyncronicity and JQuery throws error when I try to set async: false due to negative effects in user experience.
The problem is, ajax calls to get image size return NaN in a random and very frequent way, surely due to too many concurrent connections.
Is there a way you can think to overcome this?
This is my code (it was much shorter in origin, callback approach was based on this post, but didn't work):
function getPageSize(callback)
{
var pageWeight = 0;
var lastWeight = 0;
var xhr = $.ajax({
type: "HEAD",
// async: false,
url: "test01.html",
success: function(msg)
{
if (xhr.readyState == 4)
{
if (xhr.status == 200 || xhr.status == 0)
{
if ( !isNaN(xhr.getResponseHeader('Content-Length')) && !isNaN($('#size').html()) )
{
pageWeight = parseInt(xhr.getResponseHeader('Content-Length'));
callback(pageWeight);
// lastWeight = parseInt($('#size').html());
// $('#size').html(pageWeight + lastWeight);
console.log("Page " + pageWeight);
}
}
}
}
});
}
function getImagesSize(callback)
{
var imageWeight = 0;
var lastWeight = 0;
$('img').each(function()
{
var imgPath = $(this).attr('src');
xhr = null;
xhr = $.ajax(
{
type: "HEAD",
url: $(this).attr('src'),
async: false,
success: function(msg)
{
if (xhr.readyState == 4)
{
if (xhr.status == 200 || xhr.status == 0)
{
if (!isNaN(xhr.getResponseHeader('Content-Length')) && !isNaN($('#size').html()) )
{
imageWeight = parseInt(xhr.getResponseHeader('Content-Length'));
callback(imageWeight);
// lastWeight = parseInt($('#size').html());
// $('#size').html(imageWeight + lastWeight);
console.log("Image " + imgPath + ": " + imageWeight);
}
}
}
}
});
});
}
function updateTotalPageSize(size)
{
var lastWeight = 0;
lastWeight = parseInt($('#size').html());
$('#size').html(size + lastWeight);
}
$(document).ready(function()
{
getPageSize(function(size)
{
//alert(size);
updateTotalPageSize(size);
});
getImagesSize(function(size)
{
//alert(size);
updateTotalPageSize(size);
});
});
SOLUTION by #Regent
function getImagesSize(callback)
{
var allImages = $('img');
function handleNext(index)
{
if (index >= allImages.length)
{
return;
}
var imgPath = allImages.eq(index).attr('src');
$.ajax({
type: "HEAD",
url: imgPath,
success: function(msg, status, xhr)
{
if (!isNaN(xhr.getResponseHeader('Content-Length')) && !isNaN($('#size').html()))
{
var imageWeight = parseInt(xhr.getResponseHeader('Content-Length'));
callback(imageWeight);
console.log("Image " + imgPath + ": " + imageWeight);
handleNext(index + 1);
}
}
});
}
handleNext(0);
}
Related
I have logic where i am trying to add host url dynamically so it work in all env based on hosst, so below trying to find a file that is there in host but its never going into $.each statement , if call url directly http://18.35.168.87:6000/Sdk/wrapper-sdk/client/out.json it worked and rendered data, any idea what could have wrong in below code to achieve this task ?
main.js
function myFunction(val) {
var url = "../../wrapper-sdk/" + client + "/out.json";
if (window.location.hostname.indexOf("localhost") !== -1 ||
window.location.host.indexOf("localhost") !== -1) {
var scripts = document.getElementsByTagName('script');
var host = '';
$.each(scripts, function (idx, item) {
if (item.src.indexOf('Sdk/wrapper-sdk') !== -1 && (item.src.indexOf('out.json') !== -1)) {
host = item.src.split('?')[0];
host = host.replace('wrapper-sdk/' + client + '/out.json', '');
}
});
url = url.replace('../../', host);
}
$.ajax({
url: url + "?no_cache=" + new Date().getTime(),
dataType: "json",
"async": false,
success: function (data) {
console.log(data);
},
error: function () {
console.log('error while accessing api.json.')
}
});
}
I would suggest breaking up some of your checks into their own function. Makes it just a bit easier to follow the logic.
function validIp(str) {
var parts = str.split(".");
var result = true;
$.each(parts, function(i, p) {
if (parseInt(p) > 0 && parseInt(p) < 255) {
result = result && true;
}
});
return result;
}
function checkLocalUrl(str) {
var result = 0;
if (str.indexOf("localhost") >= 0) {
result = 1;
}
if (validIp(str)) {
result = -1;
}
/*
0 = Some Domain or Host Name, not LocalHost
1 = LocalHost
-1 = IP Address
*/
return result;
}
function changeSources(client) {
if (checkLocalUrl(window.location.hostname) || checkLocalUrl(window.location.host) {
var scripts = $("script");
var host = '';
scripts.each(function(i, el) {
var src = $(el).attr("src");
var nUrl = new URL(src);
var pro = nUrl.protocol;
var hn = nUrl.hostname;
if (nUrl.pathname.indexOf('/Sdk/wrapper-sdk') == 0 && nUrl.pathname.indexOf('out.json') > 0) {
host = pro + "://" + hn + "/wrapper-sdk/" + client + "/out.json";
}
$.ajax({
url: host
data: { no_cache: new Date().getTime() },
dataType: "json",
async: false,
success: function(data) {
console.log(data);
},
error: function() {
console.log('error while accessing api.json.')
}
});
});
}
}
}
See also: new URL()
You can send a string to checkLocalUrl() and it will return 1 or true if it's potentially a localhost URL. It will return 0 or false if it's any other domain pattern or -1 or false if it's an IP address.
In changeSources() we can use this to check for local urls and perform the AJAX you defined.
I want to display data stored in ch ! But my problem is that ch is displayed before the data is stored !
I think this is an Asynchronous Problems! How can I solve this problem.
When I try to get length of ch, I get always 0. Even if I store data statically in ch, I get the length 0.
I think this is an Asynchronous Problems! How can I solve this problem.
function RechercheFiltrée() {
var nom = document.getElementById('nompre').value;
var matricule = document.getElementById('matcle').value;
$.ajax({
url: "myWebServiceURL",
type: "GET",
dataType: "xml",
success: function(xml) {
var stock = [];
$(xml).find('Population').each(function() {
var table = document.getElementById("myTable");
$(this).find("directories").each(function()
{
dossier = $(this).attr('dossier');
stock.push(dossier);
});
});
var ch = [];
for (var i = 0; i < stock.length; i++) {
$.ajax({
url: "/mySecondWebServiceURL" + stock[i],
type: "GET",
dataType: "xml",
success: function(xml) {
var NMPRES = "";
var jsonObj = JSON.parse(xml2json(xml, ""));
var nom = jsonObj.SubmitResponse.occurrences.occurrence.filter(x => x["#datasection"] === "TS")[0].data.filter(x => x.item === "NMPRES")[0].value;
var matcle = jsonObj.SubmitResponse.occurrences.occurrence.filter(function(x) {
return x["#datasection"] === "LM"
})[0].data.filter(x => x.item === "MATCLE")[0].value;
var dossier = jsonObj.SubmitResponse.occurrences.occurrence.filter(function(x) {
return x["#datasection"] === "LM"
})[0]["#dossier"];
ch.push({
"nom": nom,
"matcle": matcle,
"dossier": dossier
});
if ($('#population').val() != null && firstIter == false) {
}
},
error: function(request, error) {
console.log('error Connexion : ' + error + ' request Connexion : ' + request);
}
});
}
var txt = "";
var firstIter = true;
for (var key in ch) {
if (ch[key].matcle === matricule) {
txt += "<option value='" + ch[key].dossier + "'" + firstSelect(firstIter) + ">" + ch[key].nom + "</option>";
firstIter = false;
}
}
$('#population').html(txt)
},
error: function(request, error) {
console.log('error Connexion : ' + error + ' request Connexion : ' + request);
}
});
}
The problem is that you are not waiting for the second service to respond.
It should be something like this:
const deferreds = stock.map((stockItem) => {
//... your logic with ch.push here
return $.ajax({
// your call to the second service
});
});
$.when(...deferreds).then(() => {
// your code
// for (var key in ch) {
});
The approach I'd rather take is to break the code down and use Promises. You really should take your time to learn Promises. It's a JavaScript standard and what jQuery uses under the hood.
function RechercheFiltrée() {
var nom = document.getElementById('nompre').value;
var matricule = document.getElementById('matcle').value;
return $.ajax({
url: "myWebServiceURL",
type: "GET",
dataType: "xml"
});
}
function getStockArray(xml) {
var stocks = [];
$(xml).find('Population').each(function() {
var table = document.getElementById("myTable");
$(this).find("directories").each(function() {
{
dossier = $(this).attr('dossier');
stocks.push(dossier);
});
});
});
return stocks;
}
function getStocks(stocks) {
return Promise.all(stocks.map(fetchStock));
}
function fetchStock (stock) {
return $.ajax({
url: "/mySecondWebServiceURL" + stock,
type: "GET",
dataType: "xml"
})
.then(formatStockInfo)
}
function formatStockInfo (xml) {
var NMPRES = "";
var jsonObj = JSON.parse(xml2json(xml, ""));
var nom = jsonObj.SubmitResponse.occurrences.occurrence.filter(x => x["#datasection"] === "TS")[0].data.filter(x => x.item === "NMPRES")[0].value;
var matcle = jsonObj.SubmitResponse.occurrences.occurrence.filter(function(x) {
return x["#datasection"] === "LM"
})[0].data.filter(x => x.item === "MATCLE")[0].value;
var dossier = jsonObj.SubmitResponse.occurrences.occurrence.filter(function(x) {
return x["#datasection"] === "LM"
})[0]["#dossier"];
if ($('#population').val() != null && firstIter == false) {
}
return {
"nom": nom,
"matcle": matcle,
"dossier": dossier
};
}
function updateSelectMenu (ch) {
var txt = "";
var firstIter = true;
for (var key in ch) {
if (ch[key].matcle === matricule) {
txt += "<option value='" + ch[key].dossier + "'" + firstSelect(firstIter) + ">" + ch[key].nom + "</option>";
firstIter = false;
}
}
$('#population').html(txt)
}
RechercheFiltrée()
.then(getStockArray)
.then(getStocks)
.done(updateSelectMenu);
I am just wondering why one has to use a temporary variable "that" (initialized with the currently allocated object i.e. "this") in the script below:
$(document).ready(function() {
function ChatViewModel() {
var that = this;
that.userName = ko.observable('');
that.chatContent = ko.observable('');
that.message = ko.observable('');
that.messageIndex = ko.observable(0);
that.activePollingXhr = ko.observable(null);
var keepPolling = false;
that.joinChat = function() {
if (that.userName().trim() != '') {
keepPolling = true;
pollForMessages();
}
}
function pollForMessages() {
if (!keepPolling) {
return;
}
var form = $("#joinChatForm");
that.activePollingXhr($.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false,
success: function(messages) {
console.log(messages);
for (var i = 0; i < messages.length; i++) {
that.chatContent(that.chatContent() + messages[i] + "\n");
that.messageIndex(that.messageIndex() + 1);
}
},
error: function(xhr) {
if (xhr.statusText != "abort" && xhr.status != 503) {
resetUI();
console.error("Unable to retrieve chat messages. Chat ended.");
}
},
complete: pollForMessages
}));
$('#message').focus();
}
that.postMessage = function() {
if (that.message().trim() != '') {
var form = $("#postMessageForm");
$.ajax({url: form.attr("action"), type: "POST",
data: "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(),
error: function(xhr) {
console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText);
}
});
that.message('');
}
}
that.leaveChat = function() {
that.activePollingXhr(null);
resetUI();
this.userName('');
}
function resetUI() {
keepPolling = false;
that.activePollingXhr(null);
that.message('');
that.messageIndex(0);
that.chatContent('');
}
}
//Activate knockout.js
ko.applyBindings(new ChatViewModel());
});
Why can't I just use "this"? Can anyone please explain?
this always refers to the object that is in scope when the call has been made, and this can change depending on your code. If you want it to still be your object in a sub-function, then assigning it to a variable that won't change in value gets around this issue.
This refers to the owner.
You can rewrite your code like this :
$(document).ready(function() {
function ChatViewModel() {
var that = this;
this.userName = ko.observable('');
this.chatContent = ko.observable('');
this.message = ko.observable('');
this.messageIndex = ko.observable(0);
this.activePollingXhr = ko.observable(null);
var keepPolling = false;
this.joinChat = function() {
if (that.userName().trim() != '') {
keepPolling = true;
pollForMessages();
}
}
function pollForMessages() {
if (!keepPolling) {
return;
}
var form = $("#joinChatForm");
this.activePollingXhr($.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false,
success: function(messages) {
console.log(messages);
for (var i = 0; i < messages.length; i++) {
that.chatContent(that.chatContent() + messages[i] + "\n");
that.messageIndex(that.messageIndex() + 1);
}
},
error: function(xhr) {
if (xhr.statusText != "abort" && xhr.status != 503) {
resetUI();
console.error("Unable to retrieve chat messages. Chat ended.");
}
},
complete: pollForMessages
}));
$('#message').focus();
}
this.postMessage = function() {
if (that.message().trim() != '') {
var form = $("#postMessageForm");
$.ajax({url: form.attr("action"), type: "POST",
data: "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(),
error: function(xhr) {
console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText);
}
});
that.message('');
}
}
this.leaveChat = function() {
that.activePollingXhr(null);
resetUI();
that.userName('');
}
function resetUI() {
keepPolling = false;
that.activePollingXhr(null);
that.message('');
that.messageIndex(0);
that.chatContent('');
}
}
//Activate knockout.js
ko.applyBindings(new ChatViewModel());
//fixing bracet
});
Check this link: http://www.quirksmode.org/js/this.html
I have the following script (see below). I have two questions regarding it:
1.What does the following line mean in the context of Knockoutjs?
ko.observable(null);
2.How can I invoke a function not yet defined as in here:
that.activePollingXhr(...
Here is the full script:
$(document).ready(function() {
function ChatViewModel() {
var that = this;
that.userName = ko.observable('');
that.chatContent = ko.observable('');
that.message = ko.observable('');
that.messageIndex = ko.observable(0);
that.activePollingXhr = ko.observable(null);
var keepPolling = false;
that.joinChat = function() {
if (that.userName().trim() != '') {
keepPolling = true;
pollForMessages();
}
}
function pollForMessages() {
if (!keepPolling) {
return;
}
var form = $("#joinChatForm");
that.activePollingXhr($.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false,
success: function(messages) {
console.log(messages);
for (var i = 0; i < messages.length; i++) {
that.chatContent(that.chatContent() + messages[i] + "\n");
that.messageIndex(that.messageIndex() + 1);
}
},
error: function(xhr) {
if (xhr.statusText != "abort" && xhr.status != 503) {
resetUI();
console.error("Unable to retrieve chat messages. Chat ended.");
}
},
complete: pollForMessages
}));
$('#message').focus();
}
that.postMessage = function() {
if (that.message().trim() != '') {
var form = $("#postMessageForm");
$.ajax({url: form.attr("action"), type: "POST",
data: "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(),
error: function(xhr) {
console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText);
}
});
that.message('');
}
}
that.leaveChat = function() {
that.activePollingXhr(null);
resetUI();
this.userName('');
}
function resetUI() {
keepPolling = false;
that.activePollingXhr(null);
that.message('');
that.messageIndex(0);
that.chatContent('');
}
}
//Activate knockout.js
ko.applyBindings(new ChatViewModel());
});
ko.observable(null); creates an observable with a value of null. Nothing different than ko.observable(5);, where the value would be 5.
I see that you're using the that.activePollingXhr observable by passing it the result of an ajax call. However, this call is asynchronous and $.ajax doesn't return the data it got from the server, but rather a jquery deferred. You need to use that.activePollingXhr insude the success callback. Here's is how your code might look like:
$.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false,
success: function(messages) {
console.log(messages);
for (var i = 0; i < messages.length; i++) {
that.chatContent(that.chatContent() + messages[i] + "\n");
that.messageIndex(that.messageIndex() + 1);
}
that.activePollingXhr(messages); // <-- Note where the call to activePollingXhr is
},
error: function(xhr) {
if (xhr.statusText != "abort" && xhr.status != 503) {
resetUI();
console.error("Unable to retrieve chat messages. Chat ended.");
}
},
complete: pollForMessages
});
As for the comment under your question: that.activePollingXhr is defined as that.activePollingXhr = ko.observable(null); - an observable with value of null.
That just initializes an observable with null as the initial value.
If you need to invoke a function that is an observable, just add a second set of parenthesis.
that.activePollingXhr()()
I needed to aid authentication headers for my audio files i was grabbing from a external server. So now im trying to use ajax, i can grab the files fine, but i cant set them as the media source for my player. How do you approach setting a ajax loaded file as a audio source?
EDIT
Ended up fixing it in case someone comes back this way.
if (this.mAudioPlayer.canPlayType("audio/mpeg")) {
this.mExtension = '.mp3';
}else if (this.mAudioPlayer.canPlayType("audio/ogg")) {
this.mExtension = '.ogg';
} else if (this.mAudioPlayer.canPlayType("audio/mp4")) {
this.mExtension = '.m4a';
}
this.CreateAudioData = function() {
//downloading audio for use in data:uri
$.ajax({
url: aAudioSource + this.mExtension + '.txt',
type: 'GET',
context: this,
async: false,
beforeSend: function(xhr) {xhr.setRequestHeader('Authorization', window.userId);},
success: this.EncodeAudioData,
error: function(xhr, aStatus, aError) { HandleError('Audio Error: ' + aStatus); }
});
};
this.EncodeAudioData = function(aData) {
//this.mAudioData = base64_encode(aData);
this.mAudioData = aData;
if (this.mExtension == '.m4a') {
Debug("playing m4a");
this.mAudioSrc = "data:audio/mp4;base64," + this.mAudioData;
} else if (this.mExtension == '.ogg') {
Debug("playing ogg");
this.mAudioSrc = "data:audio/ogg;base64," + this.mAudioData;
} else if (this.mExtension == '.mp3') {
Debug("playing mp3");
this.mAudioSrc = "data:audio/mp3;base64," + this.mAudioData;
}
};
this.play = function() {
if (this.mAudioPlayer.src != this.mAudioSrc) {
this.mAudioPlayer.src = this.mAudioSrc;
}
this.mAudioPlayer.load();
this.mAudioPlayer.play();
};
Had to do asynch:false, otherwise i would get a small chunk of the audio instead of all of it. Though removing the asynch made debugging easier in the end.
Are you actually downloading the file, or returning it in base64 encoded format (i.e. as a Data URI)?
Changing the source of an audio element via JavaScript is quite straightforward.
<audio id="myAudio" controls />
And then once you have the source,:
var audio = document.getElementById("myAudio");
audio.src = myAudioFile;
audio.type = "type/ogg"; // ony showing an OGG example here
if (this.mAudioPlayer.canPlayType("audio/mpeg")) {
this.mExtension = '.mp3';
}else if (this.mAudioPlayer.canPlayType("audio/ogg")) {
this.mExtension = '.ogg';
} else if (this.mAudioPlayer.canPlayType("audio/mp4")) {
this.mExtension = '.m4a';
}
this.CreateAudioData = function() {
//downloading audio for use in data:uri
$.ajax({
url: aAudioSource + this.mExtension + '.txt',
type: 'GET',
context: this,
async: false,
beforeSend: function(xhr) {xhr.setRequestHeader('Authorization', window.userId);},
success: this.EncodeAudioData,
error: function(xhr, aStatus, aError) { HandleError('Audio Error: ' + aStatus); }
});
};
this.EncodeAudioData = function(aData) {
//this.mAudioData = base64_encode(aData);
this.mAudioData = aData;
if (this.mExtension == '.m4a') {
Debug("playing m4a");
this.mAudioSrc = "data:audio/mp4;base64," + this.mAudioData;
} else if (this.mExtension == '.ogg') {
Debug("playing ogg");
this.mAudioSrc = "data:audio/ogg;base64," + this.mAudioData;
} else if (this.mExtension == '.mp3') {
Debug("playing mp3");
this.mAudioSrc = "data:audio/mp3;base64," + this.mAudioData;
}
};
this.play = function() {
if (this.mAudioPlayer.src != this.mAudioSrc) {
this.mAudioPlayer.src = this.mAudioSrc;
}
this.mAudioPlayer.load();
this.mAudioPlayer.play();
};
Had to do asynch:false, otherwise i would get a small chunk of the audio instead of all of it. Though removing the asynch made debugging easier in the end.