I got it to work with everyone's help. I change the 'context:' to 'this' from 'this.parentNode'. I still get confused with the 'this' context. With limited testing it appears to have fixed my problem with running multiple instances. Thanks for your help. The new code is shown below.
I am new to jQuery and Javascript. I am creating a general object to navigate database tables (NavDb). It works perfectly if I create 1 instance. When I run multiple instances it fails. I traced the problem to how I use 'this'. One routine that initializes/handles ajax requests fails. A form can have any number of selectors (autocomplete or drop-downs). The routine recursively performs ajax requests until all the selectors have been initialized. The 'this' variable refers to the ajax object when entering the 'success:' function. I need a reference to the parent object so I created a $this on line 2. The problem is it creates a closure and messes up the second instance (I believe that is what is happening). How do I get a reference to the parent object inside the success function? Can I bind the ajax request to the parent object? I need something like this:
var $this = this.parent;
Hopefully I explained this clearly.
New code
NavDb.prototype.getSelData = function () {
if (this.curSelector >= this.selectors.length) {
return;
}
else {
var sql = this.selectors[this.curSelector].sql;
$.ajax({
url: 'php/select.php',
type: 'POST',
context: this, // Only needed 'this' not this.parentNode.
dataType: 'json',
data: {
'sql': sql
}
}).done(function (data) {
if (data.success) {
if (data.v.length > 0) {
this.selectors[this.curSelector].data = data;
if (this.selectors[this.curSelector].type == "autoComp") {
this.initAC();
};
if (this.selectors[this.curSelector].type == "dropDown") {
this.initDD();
};
}
}
this.curSelector++;
this.getSelData();
}).fail(function (XHR, textStatus, errorThrown) {
$("#status").html(getErrorText(XHR.responseText));
});
};
};
Old code
NavDb.prototype.ajaxSelData = function () {
var $this = this;
if (this.curSelector >= this.selectors.length) {
$this = null;
return;
}
else {
var sql = $this.selectors[$this.curSelector].sql;
$.ajax({
url: 'php/select.php',
type: 'POST',
dataType: 'json',
data: {
'sql': sql
},
success: function (data) {
if (data.success) {
if (data.v.length > 0) {
$this.selectors[$this.curSelector].data = data;
if ($this.selectors[$this.curSelector].type == "autoComp") {
$this.initAC();
};
if ($this.selectors[$this.curSelector].type == "dropDown") {
$this.initDD();
};
}
} else {
alert(data.error);
}
$this.curSelector++;
$this.ajaxSelData();
}
});
};
};
For correct context scope see this answer.
You can ensure correct context in several ways:
use context
$.ajax({
url: 'php/select.php',
type: 'POST',
context: this.parentNode,
dataType: 'json',
data: {
sql: sql
},
success: function (data) {
// 'this' is parentNode
}
})
use closure
var parentNode = this.parentNode;
success: function (data) {
//you can now use 'parentNode'
}
use $.proxy
$.proxy(function(data){
// 'this' is parentNode
}, this.parentNode);
The closure your code creates is unique to each instance (it creates a separate closure for each instance) so your theory that the closure was messing up the second instance is not correct.
So, creating your separate variable as you were doing with:
var $this = this;
is a perfectly fine thing to do and will not cause problems.
But, if you want the parent node, then perhaps you should be doing this:
var parent = this.parent;
and then refer to the parent variable inside your ajax function.
You could also just pass the context argument to your ajax call and that will set the this parameter as desired in the success handler callback.
$.ajax({
url: 'php/select.php',
type: 'POST',
dataType: 'json',
context: this.parent, // add this line
data: {
'sql': sql
},
success: function (data) {
// now the this pointer is set as desired in your callback here
if (data.success) {
Related
I am working on the jquery to call a function to get the return value that I want to store for the variable email_number when I refresh on a page.
When I try this:
function get_emailno(emailid, mailfolder) {
$.ajax({
url: 'getemailnumber.php',
type: 'POST',
data : {
emailid: emailid,
mailfolder: mailfolder
},
success: function(data)
{
email_number = data;
}
});
return email_number;
}
I will get the return value as 6 as only when I use alert(email_number) after the email_number = data;, but I am unable to get the value outside of a function.
Here is the full code:
var email_number = '';
// check if page refreshed or reloaded
if (performance.navigation.type == 1) {
var hash = window.location.hash;
var mailfolder = hash.split('/')[0].replace('#', '');
var emailid = 'SUJmaWg4RTFRQkViS1RlUzV3K1NPdz09';
get_emailno(emailid, mailfolder);
}
function get_emailno(emailid, mailfolder) {
$.ajax({
url: 'getemailnumber.php',
type: 'POST',
data : {
emailid: emailid,
mailfolder: mailfolder
},
success: function(data)
{
email_number = data;
}
});
return email_number;
}
However, I have been researching and it stated that I would need to use callback via ajax but I have got no idea how to do this.
I have tried this and I still don't get a return value outside of the get_emailno function.
$.ajax({
url: 'getemailnumber.php',
type: 'POST',
async: true,
data : {
emailid: emailid,
mailfolder: mailfolder
},
success: function(data)
{
email_number = data;
}
});
I am getting frustrated as I am unable to find the solution so I need your help with this. What I am trying to do is I want to call on a get_emailno function to get the return value to store in the email_number variable.
Can you please show me an example how I could use a callback function on ajax to get the return value where I can be able to store the value in the email_number variable?
Thank you.
From the jquery documentation, the $.ajax() method returns a jqXHR object (this reads fully as jquery XMLHttpRequest object).
When you return data from the server in another function like this
function get_emailno(emailid, mailfolder) {
$.ajax({
// ajax settings
});
return email_number;
}
Note that $.ajax ({...}) call is asynchronous. Hence, the code within it doesn't necessarily execute before the last return statement. In other words, the $.ajax () call is deferred to execute at some time in the future, while the return statement executes immediately.
Consequently, jquery specifies that you handle (or respond to) the execution of ajax requests using callbacks and not return statements.
There are two ways you can define callbacks.
1. Define them within the jquery ajax request settings like this:
$.ajax({
// other ajax settings
success: function(data) {},
error: function() {},
complete: function() {},
});
2. Or chain the callbacks to the returned jqXHR object like this:
$.ajax({
// other ajax settings
}).done(function(data) {}).fail(function() {}).always(function() {});
The two methods are equivalent. success: is equivalent to done(), error: is equivalent to fail() and complete: is equivalent to always().
On when it is appropriate to use which function: use success: to handle the case where the returned data is what you expect; use error: if something went wrong during the request and finally use complete: when the request is finished (regardless of whether it was successful or not).
With this knowledge, you can better write your code to catch the data returned from the server at the right time.
var email_number = '';
// check if page refreshed or reloaded
if (performance.navigation.type == 1) {
var hash = window.location.hash;
var mailfolder = hash.split('/')[0].replace('#', '');
var emailid = 'SUJmaWg4RTFRQkViS1RlUzV3K1NPdz09';
get_emailno(emailid, mailfolder);
}
function get_emailno(emailid, mailfolder) {
$.ajax({
url: 'getemailnumber.php',
type: 'POST',
data : {
emailid: emailid,
mailfolder: mailfolder
},
success: function(data)
{
// sufficient to get returned data
email_number = data;
// use email_number here
alert(email_number); // alert it
console.log(email_number); // or log it
$('body').html(email_number); // or append to DOM
}
});
}
I am making an ajax form submission inside an object.
When I try call the other object methods within the jQuery.ajax.success callback, this line throws an error...
Is this a scoping issue?
this.DisplayError(data.error);
this.DisplayError is not a function
Code:
var toolsform = new function() {
this.sumbitUrl = 'submit.php';
this.DisplayError = function(errorMsg) {
jQuery('#trialFormError').html('<strong>Error: </strong>' + errorMsg);
}
this.AjaxSumbit = function() {
let formData = jQuery("#trialsToolsRegisterForm").serialize();
formData += '&toolsFormSumbit=1';
jQuery.ajax({
type: "POST",
url: this.sumbitUrl,
dataType: 'json',
data: formData,
success: function(data) {
if(data.success === false) {
this.DisplayError(data.error);
}
console.log(data); // show response from the php script.
}
});
}
}
Use arrow function:
success: () => {
}
This happens because you are loosing context, Ajax assigns different context when calling success function. You can also save context in New variable:
var self = this;
And use it inside success function instead of this.
Or you can define function context:
success: (function() {
}).bind(this)
this inside the success function is not equal to this outside. The simplest way to handle this is using arrow function.
var toolsform = new function() {
this.sumbitUrl = 'submit.php';
this.DisplayError = function(errorMsg) {
jQuery('#trialFormError').html('<strong>Error: </strong>' + errorMsg);
}
this.AjaxSumbit = function() {
let formData = jQuery("#trialsToolsRegisterForm").serialize();
formData += '&toolsFormSumbit=1';
jQuery.ajax({
type: "POST",
url: this.sumbitUrl,
dataType: 'json',
data: formData,
success: data => {
if(data.success === false) {
this.DisplayError(data.error); // now `this` should be equal to the one outside
}
console.log(data); // show response from the php script.
}
});
}
}
The magic is, arrow function does not has its own this. When you call this, it will refer to the outside one. But normal function has its own this, therefore when you call this, it will refer to its own this instead of the outside one.
For details, you may take a look on the MDN web docs
I have two functions that makes Ajax calls: getData and getMoreData. getMoreData requires a url variable that is dependent on the url variable getData. This questions continues from: String append from <select> to form new variable.
By appending an item obtained from the received from getData onto a base URL, I create a new variable (Let's call this NewDimensionURL) that I use for getMoreData url. However, NewDimensionURL will show error because the original list (from getData) has yet to be populated and will append nothing onto the base URL.
An idea that I have is to set NewDimensionalURL once getData finishes populating the combobox, so that getMoreData can run after.
JavaScript
var GetDimensions = 'SomeURL1';
//--Combines URL of GetDimensionValues with #dimensionName (the select ID)
var UrlBase = "Required URL of getMoreData";
var getElement = document.getElementById("dimensionName");
var GetDimensionValues = UrlBase + getElement.options[getElement.selectedIndex].text;
function handleResults(responseObj) {
$("#dimensionName").html(responseObj.DimensionListItem.map(function(item) {
return $('<option>').text(item.dimensionDisplayName)[0];
}));
}
function handleMoreResults (responseObj) {
$("#dimensionId").html(responseObj.DimensionValueListItem.map(function(item) {
return $('<option>').text(item.dimensionValueDisplayName)[0];
}));
}
function getData() {
debugger;
jQuery.ajax({
url: GetDimensions,
type: "GET",
dataType: "json",
async: false,
success: function (data) {
object = data;
handleResults(data);
}
});
}
function getMoreData() {
debugger;
jQuery.ajax({
url: GetDimensionValues,
type: "GET",
dataType: "json",
async: false,
success: function (data) {
object = data;
handleMoreResults (data);
}
});
}
Answered
Reordered as:
var GetDimensionValues;
function handleResults(responseObj) {
$("#dimensionName").html(responseObj.DimensionListItem.map(function(item) {
return $('<option>').text(item.dimensionDisplayName)[0];
}));
GetDimensionValues = UrlBase + getElement.options[getElement.selectedIndex].text;
}
Created onchange function Repopulate() for getMoreData() to parse and for handleMoreResults() to populate.
I'm guessing you just do getData(); getMoreData() back to back? If so, then you're running getmoreData BEFORE getData has ever gotten a response back from the server.
You'll have to chain the functions, so that getMoreData only gets executed when getData gets a response. e.g.
$.ajax($url, {
success: function(data) {
getMoreData(); // call this when the original ajax call gets a response.
}
});
Without seeing your code it's hard to say if this is the right solution, but you should try chaining the functions:
$.ajax({url: yourUrl}).then(function (data) {
// deal with the response, do another ajax call here
}).then(function () {
// or do something here
});
I have getGeneral function that calls ajax GET. When ajax recieves data (json), it creates KO model from given json and returns created KO.
When Knockout model is created and values are assigned, knockout applybindings should be called. Here is my code:
Defines GeneralModel and some related functions (inside "GeneralModel.js"):
var GeneralModel = function() {
//for now it is empty as data ar binded automatically from json
// CountryName is one of the properties that is returned with json
}
function getGeneral(pid) {
$.ajax({
url: "/api/general",
contentType: "text/json",
dataType: "json",
type: "GET",
data: { id: pid},
success: function (item) {
var p = new GeneralModel();
p = ko.mapping.fromJS(item);
return p;
},
error: function (data) {
}
});
}
This is called from another file (GeneralTabl.html), it should call get function and applyBindings to update UI:
var PortfolioGeneral = getGeneral("#Model.Id");
ko.applyBindings(PortfolioGeneral, document.getElementById("pv-portfolio-general-tab"));
However, in this scenario I am getting error (CountryName is not defined). This is because applyBindings happens before ajax returns data, so I am doing applyBindings to empty model with undefined properties.
Mapping from Json to Model happens here and is assignes values:
p = ko.mapping.fromJS(item);
I can also fill in GeneralModel with all fields, but it is not necessary (I guess):
var GeneralModel = function() {
CountryName = ko.observable();
...
}
It will still give an error "CountryName is not defined".
What is the solution?
1) Can I somehow move getGeneral inside GeneralModel, so get data would be part of GeneralModel initialization?
or
2) Maybe I should somehow do "wait for ajax results" and only then applyBindings?
or
I believe there are other options, I am just not so familiar with KO and pure JS.
Note: I fully understand that this is because Ajax is Async call, so the question is how to restructure this code taking into account that I have two seperate files and I need to call getGeneral from outside and it should return some variable.
Try using the returned promise interface:
function getGeneral(pid) {
return $.ajax({
url: "/api/general",
contentType: "text/json",
dataType: "json",
type: "GET",
data: {
id: pid
}
});
}
getGeneral("#Model.Id").done(function (item) {
var p = new GeneralModel();
p = ko.mapping.fromJS(item);
ko.applyBindings(p, document.getElementById("pv-portfolio-general-tab"));
}).fail(function () {
//handle error here
});
Im trying to develop a class in JavaScript I can use to access a load of data that is gathered by an AJAX request easily. The only problem is I need to make the members of the class accessible only once the AJAX call is complete. Ideally what I would like to end up is something where by I can call this in a script:
courses.getCourse('xyz').complete = function () {
// do something with the code
}
And this will only fire after the AJAX call has been complete and the data structures in the "class" are ready to be used. Ideally I dont want to have to create a .complete member for every function in the class
Here is the "class" I am trying to make so far:
var model_courses = (function() {
var cls = function () {
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = new Array(); // Stores all available course IDs
var _ready = 0;
$.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
})
}
})
console.log(_storage.courses)
}
cls.prototype = {
getCourse: function (courseID) {
console.log(cls._storage)
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
At the moment getCourse will be fired before the AJAX request is complete and obviously it will have no data to access.
Any ideas will be greatly appreciated, im stuck on this one!
jQuery already handles this for you using deferred objects, unless i'm misunderstanding what you are looking for.
var courses = {
getCourse: function (id) {
return $.ajax({url:"getCourse.php",data:{id:id});
}
};
courses.getCourse("history").done(function(data){
console.log(data);
});
I know this isn't exactly what you are looking for, I'm hoping it's enough to push you in the right direction. Deferred objects are awesome.
The following changes allow you to make the AJAX request just once and you can call your function like
courses.getCourse('xyz', function(course){
// Use course here
});
Here are the changes
var model_courses = (function() {
// This is what gets returned by the $.ajax call
var xhr;
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = []; // Stores all available course IDs
var cls = function () {
xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
});
}
});
}
cls.prototype = {
// Made changes here, you'd have to make the same
// changes to getCourses and getReferences
getCourse: function (courseID, callback) {
if (xhr.readyState == 4) {
callback(_storage.courses[courseID]);
}
else {
xhr.done(function(){
callback(_storage.courses[courseID]);
})
}
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
As a side note, your module pattern will not work very well if you need to instantiate two of these model_courses objects, since the storage objects are all shared in your self calling function's closure. You usually don't mix the module pattern with prototypes (returning a constructor from a module), unless you really know what you are doing, that is, the shared closure variables work as static properties of your class.
This is what I would do if I were you (since you really want private variables)
function ModelCourses() {
var storage = {
courses: {},
references: []
};
var xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
storage.references.push($(this).attr('id'))
})
}
});
this.getCourse = function(courseId, callback) {
function getCourse() {
callback(storage.courses[courseID])
}
if (xhr.readyState == 4) {
getCourse();
}
else {
xhr.done(getCourse);
}
};
}
in getStorage either add a check to see if there is any data to pilfer (preferred), or make the "actual" method private than publicize it when it has items it can access. (I would recommend the first though otherwise you'll get exceptions about calling a method that doesn't exists on an object).
You can define a function getData that would perform the ajax request and that would take the getCourse as a callback.
The getData could possibly store locally the result of the Ajax call and test the local storage before performing the ajax call.
You could also specify a private member to allow the ajax call to be run only once.
You might want to check underscore.js for some handy tool
Here is a short example code :
cls.prototype.getData = function(callback) {
/*perform ajax call or retrieve data from cache*/
callback()
}
cls.prototype.getCourse = function(id) {
this.getData(function() {
/*do something with the data and the id you passed*/
}
}