AJAX (XMLHTTPRequest) time delay - javascript

I am new to AJAX and have this issue.
I am creating a photogallery page and on it I created a Javascript class which manager the whole thing. Here is a method from the class which calls XMLHTTPRequest object which was initialized successfully earlier:
this.AJAX_update = function(id) {
//initialize AJAX - this is done successfully
this.AJAX_initialize();
var url = "ScriptLibrary/gallery_update.php?img=" + id;
//this.ajax_request is an internal variable which is
//initialized in this.AJAX_initialize() and is
//XMLHTTPRequest type
this.ajax_request.open("GET", url, true);
this.ajax_request.onreadystatechange = processAJAX;
this.ajax_request.send(null);
}
I could not figure out how to call class' internal method in onreadystatechange, so I created a function outside of the class which calls a method inside the class provided that the instance of the class has been created on the page which it is. The instance of the class on the page is the variable 'gallery'. Here is the function:
function processAJAX() {
gallery.AJAX_process();
}
Here is the code of the AJAX_process() function:
this.AJAX_process = function() {
if (this.ajax_request.readyState == 4) {
if (this.ajax_request.status == 200) {
//get the response
var response = this.ajax_request.responseXML;
//Here I set the internal variables according to the value
//returned from the server
//...........
//...........
//...........
//change image on the page
var self = this;
setTimeout(function() {
self.swap_dissolve();
}, 50);
}
}
}
So, here is my question:
Initially I tried to call the internal function directly by just doing this.swap_dissolve() however it does not work. Basically the AJAX_process() method changes values of certain class' internal variables according to the response from the server, and those values are used in the swap_dissolve() to actually change the image on the page. What would happen is that swap_dissolve() would not change the image correctly because it was seeing null or not updated values of the variables AJAX_process() updated. When I added the time delay, the problem disappeared. I don't understand why that is happening. I do check that the readyState == 4, so the response from the server has been completely returned, therefore whatever values the function sets, are final and can be used right away, so why can't I use those values right away and have to wait?
Any suggestions or faults in my code to resolve this issue would be highly appreciated.
If you have any question to me, please post response to this question.
Thank you.

You want to use a 'closure' (e.g. pass a function-object to setTimeout).
Imagine this (heck, substitute it in your code to see):
// when response = 200
var someValue = "blahblah" // whatever you read in
setTimeout(function () {
alert(someValue)
}, 1000)
The alert should display "blablah" (or whatever you assigned to it). The value is available to the anonymous function object because a closure-binding has been created (the function can access the variables of the enclosing function scope(s).
Here is a reference: http://jibbering.com/faq/notes/closures/ and more of a soft intro: http://blog.morrisjohns.com/javascript_closures_for_dummies.html
Happy coding.
Edit/P.S. You can also write the AJAX handler like (it's just a closure to force the correct 'this' context -- some frameworks offer convenience functions for this):
var self = this
this.ajax_request.onreadystatechange = function () {
self.process_AJAX()
}
You can even use a double-binding, but... happy coding, again :-)

Related

How to provide an XMLHttpRequest promise to a JavaScript function

Messing around with an autocomplete plugin available at https://www.npmjs.com/package/bootstrap-4-autocomplete, and the following works:
$('#id').autocomplete({
source: {'test1':1, 'test2':2, 'test1':3}
});
Instead of local JSON, will need to make an XMLHttpRequest and was thinking something like the following, and while I don't get an error, I also don't get anything:
$('#id').autocomplete({
source: function() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
return JSON.parse(this.responseText);
}
};
xhttp.open(method, url, true);
xhttp.send();
}
});
The plugin's author made the following remark a while back:
I don't have plans to directly invoke any url inside the lib. What you
can do is set autocomplete to your textfield after your ajax call
returns, which you can do with jQuery, like this:
$.ajax('myurl').then((data) => $('#myTextfield').autocomplete({
source: data }));
You don't have to worry about setting autocomplete to a field multiple
times, it is supposed to work like this when you need to change the
source.
Tried it and as expected, $.ajax() initiated an XMLHttpRequest request upon page load, and not as desired when the user enters a character into the search input.
How am I able to make an XMLHttpRequest to source the data into the plugin? I am assuming that I should be using a promise, however, if not, still would appreciate any assistance.
Thanks
Well, that's how plugin supposed to work. Its meat and potatoes is createItems function, called on keyup event - and responsible for filling out that dropdown with items. And here's its key part (1.3.0 version):
function createItems(field: JQuery < HTMLElement > , opts: AutocompleteOptions) {
const lookup = field.val() as string;
// ...
let count = 0;
const keys = Object.keys(opts.source);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const object = opts.source[key];
const item = {
label: opts.label ? object[opts.label] : key,
value: opts.value ? object[opts.value] : object,
};
if (item.label.toLowerCase().indexOf(lookup.toLowerCase()) >= 0) {
items.append(createItem(lookup, item, opts));
if (opts.maximumItems > 0 && ++count >= opts.maximumItems) {
break;
}
}
}
// skipped the rest
}
As you can see, each time createItems is called, it goes through source object, grepping all the items containing lookup string.
So all the data parts are expected to be there - and to be processable synchronously. That's the plugin's way, with all good and bad coming out of this approach.
The best thing the plugin's author could've suggested here (without going against what plugin is about) is using AJAX to prepopulate the data before calling autocomplete. And that's what he did in that comment actually.
Now, what can be done here? One might think it's enough just to transform createItems into an async function - for example, calling source if it's a function and expecting its result to be a Promise. It seems to be seductively simple excluding that lookup loop in process - and just take the the results of that AJAX call to repopulate source...
But that's not so simple, unfortunately: there are several caveats to be aware of. What should happen, for example, if user stops typing (triggering first AJAX call), then types some more, then stops once again (triggering another AJAX call) - but the first one actually arrives later? The corresponding bug was plaguing a lot of autocomplete implementations I've been working with, sadly - it's not that easy to reproduce if you're testing only with fast network connections (let alone only on localhost).
That's just one of the reasons the author decided against extending that plugin, it seems. After all, it was built to solve one specific task - and it does this well. So unless you want to fork it and essentially rewrite it into 'two strategies' one, I'd suggest considering looking somewhere else.

Returning empty string on a input that has a value

I have a date input in my page, which I'm using Daterangepicker framework to populate it.
Here is the code of how I start my page!
$(function(){
startSelectors();
var variaveis = returnInputVars();
var rede = variaveis[0];
var codLoja = variaveis[1];
var period = variaveis[2];
console.log('1.'+rede+' 2.'+codLoja+' 3.'+period);
});
function returnInputVars(){
var rede = $("#dropdown-parceria").val();
var codLoja = $("#dropdown-loja").val();
var periodo = $("#datepicker-range").val();
return [rede, codLoja, periodo];
};
The function startSelectors() is set to start my datepicker and other fields, which is working perfectly. After it, I create a var called "variaveis" to fill
with the values of each field because I will use then later (this functions also works perfectly at other scripts of my page).
Running the page, my console returns this:
The funny thing is, if I type at the console this, the value is shown, just while starting the script is does not work!
Anybody experienced something like this?
***UPDATE
Adding this script to my start function:
console.log($("#datepicker-range"));
The value is shown, but the second console.log don't:
EDIT 1. FIDDLE (Suggested by #halleron)
To ensure things are loaded in the correct order, it is useful to apply a page sniffer code snippet that will scan the page continuously until a condition is met, or until a preset counter limit is reached (to prevent strain on browser memory). Below is an example of what I typically use that would fit your scenario.
I think because you are dealing with asynchronous loading, you can't have a global variable that holds the values in a global scope without an interval to detect when it can be used. Otherwise, it will attempt to read the variable when it is not yet ready.
You can invoke functions anywhere you like. But I would keep all of your variables contained within the page_sniffer_2017() because that is a controlled environment where you know that everything successfully loaded and you know that the variables are ready to be accessed without error.
That way, regardless of connection speed, your functions will only fire when ready and your code will flow, sequentially, in the right order.
Within the ajax success options, always add a class to the body of the document that you can search on to determine if it has finished loading.
$(document).ready(function() {
page_sniffer_2017();
});
function page_sniffer_2017() {
var counter = 0;
var imgScanner = setInterval(function() {
if ($("#datepicker-range").length > 0 && $("#datepicker-range").val().length && jQuery('body').hasClass('date-picker-successfully-generated')) {
var periodoDatepicker = $("#datepicker-range").val(); // ok
console.log(periodoDatepicker); // ok
var variaveis = returnInputVars(replaceDate(periodoDatepicker)); // ok
console.log(variaveis[0], variaveis[1], variaveis[2]);
//startNewSelectors(variaveis);
// start ajax call
generateData(variaveis[0], variaveis[1], variaveis[2]);
clearInterval(imgScanner);
} else {
//var doNothing = "";
counter++;
if (counter === 100) {
console.log(counter);
clearInterval(imgScanner);
}
}
}, 50);
}

Ajax call in "for" loops skips odd/even iterations

If I am here asking it is because we are stuck on something that we do not know how to solve. I must admit, we already searched in StackOverflow and search engines about a solution.. but we didn't manage to implement it / solve the problem.
I am trying to create a JavaScript function that:
detects in my html page all the occurrences of an html tag: <alias>
replaces its content with the result of an Ajax call (sending the
content of the tag to the Ajax.php page) + localStorage management
at the end unwraps it from <alias> tag and leaves the content returned from ajax call
the only problem is that in both cases it skips some iterations.
We have made some researches and it seems that the "problem" is that Ajax is asynchronous, so it does not wait for the response before going on with the process. We even saw that "async: false" is not a good solution.
I leave the part of my script that is interested with some brief descriptions
// includes an icon in the page to display the correct change
function multilingual(msg,i) {
// code
}
// function to make an ajax call or a "cache call" if value is in localStorage for a variable
function sendRequest(o) {
console.log(o.variab+': running sendRequest function');
// check if value for that variable is stored and if stored for more than 1 hour
if(window.localStorage && window.localStorage.getItem(o.variab) && window.localStorage.getItem(o.variab+'_exp') > +new Date - 60*60*1000) {
console.log(o.variab+': value from localStorage');
// replace <alias> content with cached value
var cached = window.localStorage.getItem(o.variab);
elements[o.counter].innerHTML = cached;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(window.localStorage.getItem(o.variab),o.counter);
} else {
console.log(o.variab+': starting ajax call');
// not stored yet or older than a month
console.log('variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language);
$.ajax({
type: 'POST',
url: my_ajax_url,
data: 'variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language,
success: function(msg){
// ajax call, storing new value and expiration + replace <alias> inner html with new value
window.localStorage.setItem(o.variab, msg);
var content = window.localStorage.getItem(o.variab);
window.localStorage.setItem(o.variab+'_exp', +new Date);
console.log(o.variab+': replacement from ajax call');
elements[o.counter].innerHTML = content;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(msg,o.counter);
},
error: function(msg){
console.warn('an error occured during ajax call');
}
});
}
};
// loop for each <alias> element found
//initial settings
var elements = document.body.getElementsByTagName('alias'),
elem_n = elements.length,
counter = 0;
var i = 0;
for(; i < elem_n;i++) {
var flag = 0;
console.info('var i='+i+' - Now working on '+elements[i].innerHTML);
sendRequest({
variab : elements[i].innerHTML,
api_key : settings.api_key,
language : default_lang,
counter : i
});
$(elements[i]).contents().unwrap().parent();
console.log(elements[i].innerHTML+': wrap removed');
}
I hope that some of you may provide me some valid solutions and/or examples, because we are stuck on this problem :(
From our test, when the value is from cache, the 1st/3rd/5th ... values are replaced correctly
when the value is from ajax the 2nd/4th .. values are replaced
Thanks in advance for your help :)
Your elements array is a live NodeList. When you unwrap things in those <alias> tags, the element disappears from the list. So, you're looking at element 0, and you do the ajax call, and then you get rid of the <alias> tag around the contents. At that instant, element[0] becomes what used to be element[1]. However, your loop increments i, so you skip the new element[0].
There's no reason to use .getElementsByTagName() anyway; you're using jQuery, so use it consistently:
var elements = $("alias");
That'll give you a jQuery object that will (mostly) work like an array, so the rest of your code won't have to change much, if at all.
To solve issues like this in the past, I've done something like the code below, you actually send the target along with the function running the AJAX call, and don't use any global variables because those may change as the for loop runs. Try passing in everything you'll use in the parameters of the function, including the target like I've done:
function loadContent(target, info) {
//ajax call
//on success replace target with new data;
}
$('alias').each(function(){
loadContent($(this), info)
});

Can you access an object-defined property in a private method in JavaScript?

I am building out a library that is roughly laid out as follows. There are a couple of variables, an event handler and a method which results in the related event firing. Finally I extend the deal with a property. It goes like this (I've added some ///... where I've clipped out other code):
$.collidR = function (options) {
/// ...
var hubName = 'CollidRHub';
var hubProxy = connection.createHubProxy(hubName);
/// ...
hubProxy.on('registrationComplete', function (username, hasChanges) {
$(window).triggerHandler(events.onRegistrationComplete, { username: username, hasChanges: hasChanges });
log(username + " has successfully registered for this entity.");
// capture current user
this._currentUser = username;
// hook for catching up when user joins after edits
if (hasChanges) {
log("There are outstanding changes for this entity...");
}
});
/// ...
this.registerClient = function () {
/// does some stuff that makes registrationComplete fire
/// ...
};
};
Object.defineProperty($.collidR.prototype, "currentUser", {
get: function () {
return this._currentUser ? this._currentUser : "";
}
});
Note that above the this._currentUser = username bit does not seem to work. I think it's a problem with encapsulation, which is what this question is building towards.
In a separate but related library I create an instance of collidR, and I need to respond to an event. I've got the handler for that set up as follows:
$(window).on(collidR.events.onEditorsUpdated, function (e, data) {
/// ...
users.forEach(function (user) {
var currentUser = collidR.currentUser;
// here, currentUser is always default of ""
if (user != currentUser) {
/// ...
}
});
});
Here is what I am seeing:
My registrationComplete event fires and the handler is called successfully
From a debugger, this._currentUser is undefined before the value is set
The line this._currentUser = username is executed and the value is set
When the onEditorsUpdated event fires, collidR.currentUser is always the default value (an empty string) in my handler
What feels out-of-order is where I define the property -- after the rest of the object. It's as though I'm altering the prototype of the object after I define a method which tries to reference the property...this can't be right.
I've also tried this.currentUser (in the internal method), but had the same result.
I had assumed that if the prototype was extended before the internal method was invoked that when I did var currentUser = collidR.currentUser; that I would get the value from the property, but it's always an empty string.
Is there a way to register the property earlier?
Is there a correct way to set the value so that I will be able to access it later via the exposed property?
Because this in this._currentUser = username; isn't what you think it is. The value of this in JavaScript depends on how the function was called. I assume that inside the handler, it's now referring to hubProxy or some other object other than collidR.
Assuming that your entire plugin's this refers to collidR (which I highly suspect isn't, explained in the section after this), what you can do is save the context of that scope into another variable outside the handler. That way, you can refer to the outer scope's context via that variable:
// Saving this scope's context
var that = this;
hubProxy.on('registrationComplete', function (username, hasChanges) {
// access _currentUser via the saved context
that._currentUser = username;
});
However, I should warn you about using this. Assuming you are creating a plugin, you'd be calling it like $.collidR({..}). In this case, the this inside the function will refer to $ (which I assume is jQuery) and you are attaching some property to the library. It's risky to attach plugin-specific values to a global library as collisions might occur.
I suggest you store it inside a local variable/object instead.

reuse serialized reference to "this"-Keyword

First things first: I'm not sure whether the information that I'm going to provide will be enough, I will happily add additional information if needed.
I'm serializing a complex structure into the JSON-Format, Field[i][0] is the "this"-reference to an object.
Firebug's Output on JSON.Stringify(myObj)
This is all fine and working as long as I keep it all JS. But now I have the requirement to serialize and send it to my backend to get the reference + computed information back.
Now how do I map back to the reference I had before? How do I bind this ref back to an Object?
This $$hash thing looks internal and proprietarish so I havent even bothered trying something like Object[$$hash] = ref or whatever.
This general idea probably seems pretty whack, but the result is returned asynchrously and I need an identifier to bind the new information back to the original object. Obviously I could just make up my own identifier for that, but I was wondering whether there's an option to solve it this way.
EDIT
The objects are created like this (likewise)
var arrayOfObj = []
arrayOfObj.push(new Object.With.SomeSettersAndGetters());
The Object has a method like
function GetRef(){
return this;
}
Which I'm using to keep a ID/Ref through my code.
Thank you!
Update
If you want to update a series of instances and make many Ajax requests, then you need to look at Ajax long polling and queueing techniques. You won't be able to preserve the reference, but regardless of what Ajax technique you use, make use of the below trick to preserve the reference.
Add long polling on top and you're good to go.
The idea is this:
Assume the server will respond in JSON format. If you need to refer to the original references, here's my two cents:
Update the exact references when the server replies. Say you have 10 instances of Something stored in an array. On a successful response, you use the methods in the Something class to update the specific instances in whatever way you want.
/**
* The array with something instances.
* #type {Array.<Something>}
*/
var instances = [];
/**
* The Ajax success function.
* #param {Event} event The event object.
*/
function ajaxSuccess(event) {
var response = event.target.getResponseText();
var actualResponse = JSON.parse(response);
for (var i = 0, len = actualResponse.length; i++) {
instances[i].setWhatever(actualResponse[i].whatever);
};
};
The above is a more procedural approach. If you want full blown OOP in JS, then you think in modular design patterns. Say you have a module that loads data into some place. Basically, everything related to that module is an instance property.
var myModule = function() {
this.whatever = 1;
};
myModule.prototype.loadMore = function() {
var request = new XMLHttpRequest(),
that = this; // store a reference to this.
request.send(); // etc
request.onreadystatechange = that.onSucess;
};
myModule.prototype.onSucess = function(event) {
var response = JSON.parse(event.target.getResponseText());
this.whatever = response.whatever;
};
var moduleInstance = new myModule();
myModule.loadMore();
// Now the scope is always preserved. The callback function will be executed in the right scope.
Let's assume on the backend side of things, you have a model class that mimics your client side JavaScript model. Say you want to update a reference inside a model that displays text. I use Scala on the backend, but look at the fields/properties and ignore the syntax.
case class Article (
title: String,// these are my DB fields for an Article.
punchline: String,
content: String,
author: String
);
// now assume the client is making a request and the server returns the JSON
// for an article. So the reply would be something like:
{"title": "Sample title", "punchline": "whatever", "content": "bla bla bla boring", "author": "Charlie Sheen"};
// when you do
var response = JSON.parse(event.target.getResponseText());
// response will become a JavaScript object with the exact same properties.
// again, my backend choice is irrelevant.
// Now assume I am inside the success function, which gets called in the same scope
// as the original object, so it refers TO THE SAME THING.
// the trick is to maintain the reference with var that = this.
// otherwise the onSuccess function will be called in global scope.
// now because it's pointing to the same object.
// I can update whatever I want.
this.title = response.title;
this.punchline = response.punchline;
this.content = response.content;
this.author = response.author;
// or I can put it all in a single variable.
this.data = response;
What you need to remember is that scope needs to be preserved. That's the trick.
When I do var that = this; I copy a reference to the model instance. The reference is remembered through higher-order, not current scope.
Then I tell the XMLHttpRequest object to call that.ajaxSuccess when it is complete. Because I used that, the ajaxSuccess function will be called in the scope of the current object. So inside the ajaxSuccess function, this will point to the original this, the same instance.
JavaScript remembers it for me it when I write var that = this;

Categories