Related
I have a jQuery file which also uses unserscore.js. It controls the selections of dates and different venues. For one of the pages it also controls which visuals are displayed depending on the type of venue. I can successfully, using ajax, get the type of page, but I have been unable to pass that value to a public variable in the script. It is based on which WiFi spot the data is coming from. If the data is from a local spot the page should display a d3 bubble chart. If it's from a remote spot it should display a map of the venue. Currently I have the functionality working with hard coding based on the id of the venue which is far from ideal.In order to make the decision based on which spot the venue is using I created an ajax call that gets the "spot". With console.log I can see that I am getting the correct result from the ajax call, but I'm missing something in terms of passing that information to a variable so I can use it.
This is the complete jQuery files:
define([
"ui/selects",
], function (SelectsUiClass) {
var global = this;
var MainControlsClass = function () {
// Private vars
var _this = this,
_xhr = null,
_selects = new SelectsUiClass(),
_dateRangeSelect,
_venueSelect,
_floorSelect,
_zoneSelect;
// Public vars
this.Selects = null;
this.spotName = null;
// Private Methods
var _construct = function () {
_dateRangeSelect = _selects.InitSelect('#mainControls-dateRange', _onSelectChange);
_venueSelect = _selects.InitSelect('#mainControls-venue', _onSelectChange);
_floorSelect = _selects.InitSelect('#mainControls-floor', _onSelectChange);
_zoneSelect = _selects.InitSelect('#mainControls-zone', _onSelectChange);
var value = _this.GetVenue();
_getChartDisplayDiv(value);
};
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
console.log('controlsjs 36, navigation page: ' , page);
console.log('controlsjs 37, venue value: ' , venueId);
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
// Public functions
this.SetLoading = function (option) {
if (_.isUndefined(option)) { option = false; }
if (this.spotName) { this.spotName.SetLoading(option); }
};
this.Update = function (data) {
if (_.isUndefined(data) || _.isNull(data)) {
console.log('Controls 106: Spot Name: ', data)
this.spotName = data;
}
};
var _getVenueData = function (venueId) {
for (var i = 0; i < venuesData.length; i++) {
if (venuesData[i].id === venueId) {
if (!_.isUndefined(venuesData[i].spot_data)) {
return venuesData[i].spot_data;
}
}
}
};
var _onVenueChange = function () {
var value = _this.GetVenue();
if (_.isNull(value)) {
return;
}
_getChartDisplayDiv(value);
//_setSelectValue(_venueSelect, value);
var venueData = _getVenueData(value);
console.log('Venue data received: ', venueData);
if (!_.isUndefined(venueData) && !_.isUndefined(venueData.floors)) {
_selects.UpdateSelect(_floorSelect, venueData.floors);
_onFloorChange();
}
};
var _onFloorChange = function () {
var value = _this.GetFloor(),
zones = [];
if (_.isNull(value)) {
return;
}
//_setSelectValue(_floorSelect, value);
if (_.isNumber(value)) {
var venueData = _getVenueData(_this.GetVenue()),
floors = venueData.floors;
for (var i = 0; i < floors.length; i++) {
if (floors[i].id === value) {
zones = floors[i].zones;
}
}
}
_selects.UpdateSelect(_zoneSelect, zones);
};
var _onZoneChange = function () {
var value = _this.GetZone();
if (_.isNull(value)) {
return;
}
//_setSelectValue(_zoneSelect, value);
};
var _onSelectChange = function (e) {
var t = $(e.target),
id = t.attr('id');
if (_venueSelect && _venueSelect.attr('id') === id) {
_onVenueChange();
} else if (_floorSelect && _floorSelect.attr('id') === id) {
_onFloorChange();
} else if (_zoneSelect && _zoneSelect.attr('id') === id) {
_onZoneChange();
}
EventDispatcher.Dispatch('Main.Controls.Change', _this, {
caller: id
});
};
// Public Methods
this.GetDateRange = function () {
return _selects.GetSelectValue(_dateRangeSelect);
};
this.GetDateRangeKey = function () {
if (_dateRangeSelect) {
var selected = _dateRangeSelect.find('option:selected');
if (selected.length) {
return selected.attr("data-key") || "";
}
}
return "";
};
this.GetVenue = function () {
return _selects.GetSelectValue(_venueSelect);
};
this.SetVenue = function (value) {
_selects.SetSelectValue(_venueSelect, value);
}
this.GetFloor = function () {
return _selects.GetSelectValue(_floorSelect);
};
this.SetFloor = function (value) {
_selects.SetSelectValue(_floorSelect, value);
}
this.GetZone = function () {
return _selects.GetSelectValue(_zoneSelect);
};
this.SetZone = function (value) {
_selects.SetSelectValue(_zoneSelect, value);
}
this.GetData = function () {
return {
dateRange: {
date: this.GetDateRange(),
key: this.GetDateRangeKey()
},
venue: this.GetVenue(),
floor: this.GetFloor(),
zone: this.GetZone()
};
};
// Init
_construct();
};
return MainControlsClass;
});
The function that determines which visual to display is close to the top: _getChartDisplayDiv:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
When I am able to pass the "spot" information to it or a variable that it uses, it should look like this:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (_this.spotName === 'local' ) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
My ajax call is here:
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
This successfully gets the right spot, but I have been unable to pass it to a variable I can use. I think I am getting mixed up between private and public variables. I tried to use the 'this.Update' function to pass the setting to the public 'this.spotName' variable, but that comes up null. I have also tried to simply return the result of the ajax call, but I get a "not a function" error. How can I make the result of the ajax call available to my '_getChartDisplayDiv' function?
Your problem is that you are trying to read the value of _this.spotName before it is assigned. Let us walk through the steps that happen.
When you call _getChartDisplayDiv(value), the _getChartDisplayDiv function first calls _this.Load(venueId). Load, in turn, submits an ajax request with a success callback, reproduced in abbreviated form below:
this.Load = function (venueId) {
// ...
_this.SetLoading(true);
_xhr = $.ajax({
...
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
When the response arrives, the success callback will be invoked, which in turn will call _this.Update, which will set the variable you are after. The syntax you used for this purpose is correct. However!
"When the response arrives" happens to be an unpredictable event in the future. It might be after 10 milliseconds, it might take 2 seconds, or the request might time out altogether. Even 10 milliseconds is already an eternity, compared to the time it takes your browser to execute all other code in your script. You can be quite sure that by the time $.ajax returns, the success callback has not run yet.
When you pass a callback (success) to a function ($.ajax) and the callback is not run before the function returns, this is called an asynchronous callback, "async" for short. When a callback might be invoked async, it is important for the function to guarantee that it always runs async, because this type of situation needs to be handled in an entirely different way from when the callback is invoked synchronously (i.e., before the function returns). You can read more about the technicalities in this blogpost. So this is exactly what $.ajax guarantees: it will never invoke the success (or error) callback before it returns, even in the hypothetical situation that the response would arrive fast enough.
Right after $.ajax returns, your Load function returns, at which point your _getChartDisplayDiv function continues to execute. Almost immediately after that, you intend to read _this.spotName. $.ajax has already returned, so you might hope that at this point, the success callback has already been invoked.
Unfortunately for you, async callbacks are more stubborn than that. Not only does an async callback not run until the function to which you pass it returns; it does not run until any currently executing function returns. Besides $.ajax, Load needs to return, _getChartDisplayDiv needs to return, any function that was calling _getChartDisplayDiv needs to return, and so forth. The entire call stack needs to unwind. Only then (and when the response actually arrives, which is likely to be many milliseconds later) will the success callback be invoked. This game rule is called the event loop in JavaScript.
The solution is simpler than you might expect: you just need to invert the order of control. Rather than trying to force the data out of a request when you want to update the chart, you can update the chart when the response arrives, and rather than trying to update the chart directly, you can just trigger the request. Specifically in your case, you just need to make three changes:
In the places where you currently call _getChartDisplayDiv, call _this.Load instead.
Remove the line that calls _this.Load inside the _getChartDisplayDiv function.
At the end of the success handler, add a line that calls _getChartDisplayDiv.
Incidentally, using a proper application framework will make it much easier to manage this kind of thing. In your case, I recommend trying Backbone; it builds on top of Underscore and jQuery and it is unopinionated, so you can gradually adopt it without having to radically change the way you work.
I am not familiar with underscore.js. For jQuery you have two options, which you can use as an inspiration for your case. Untested code:
1. Callback function
You provide a callback function:
$('.mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123',
getType: function(type) {
console.log(type); // Example accessing internal data
}
});
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local'
getType: function() {},
// otherSettings: 'as needed',
}, opt);
// plugin code here...
if(typeof settings.getType === 'function') {
settings.getType(settings.type);
}
});
return this;
};
}( jQuery ));
2. Plugin method
You define plugin method(s) that can be called:
$('#mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123'
});
console.log($('#mydiv').myPlaugin('getType'));
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local',
// otherSettings: 'as needed',
}, opt);
this.getType = function() {
return settings.type;
}
let firstArg = arguments[0];
if(typeof firstArg === 'string') {
let func = this[firstArg];
if(typeof func === 'function') {
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
return func.apply(this, args);
}
} else {
// plugin init code here...
}
});
return this;
};
}( jQuery ));
I am having trouble getting the errorCount property to increase during code execution. The problem I am having is occurring inside of the $.ajax request, more specifically the addError() method.
If I use the following code below to check the current count of errorCount it always returns 0 even though I have manually created an error to occur. But inside of the ajax method after I call addError() and then check the value of errorCount it shows 1 like it should. What did I do wrong?
var boom = new test(settings, formData, search);
console.log(boom.errorCount);
boom.queueCalls(settings);
console.log(boom);
console.log(boom.errorCount);
Here is the object code:
function test(a, b, c) {
this.settings = a;
this.formData = b;
this.search = c;
this.errorCount = 0;
}
test.prototype = {
constructor: test,
queueEmails:function(settings, formData, search) {
var url = '/example-url-endpoint';
var data = {postData: settings + "&" + formData + "&" + search};
this.sendRequest(url, data);
},
queueCalls:function(settings) {
var url = '/example-url-endpoint2';
this.sendRequest(url, settings);
},
addMessage:function(response) {
flashMessage(response.Message, response.Result);
},
addError:function() {
this.errorCount++;
},
sendRequest:function(url, data) {
var blah = this;
j$.ajax({
type: "POST",
url: url,
data: data,
dataType: 'json',
success: function(data) {
response = JSON.parse(data);
if(response.Result != 'error') {
blah.addMessage(response);
} else {
blah.addMessage(response);
blah.addError();
console.log(blah.errorCount);
}
},
error: function(e, textStatus, errorThrown) {
blah.addError();
console.log(blah.errorCount);
alert("There was an error creating the queue");
}
});
}
}
The problem is you are doing an asynchronous (AJAX) call. When you call the queueCalls function, it makes the AJAX call, then runs your console.log statements. It does not wait until the AJAX call is done, and you have received your errors to run the console statements. If you want to do that, look at the jQuery documentation for .ajax(), and either make your AJAX call not asynchronous, or put your console statements in a .done() event handler that will fire after the AJAX call is complete.
I'm making a jquery library to use an application with the json rpc protocol but I'm stuck with a little problem.
This is the fiddle that shows the code (obviously it can't work): https://jsfiddle.net/L9qkkxLe/3/.
;(function($) {
$.lib = function(options) {
var outputHTML = [],
plugin = this;
var APIcall = function(api_method, api_params) {
request = {};
request.id = Math.floor((Math.random() * 100) + 1);
request.jsonrpc = '2.0';
request.method = api_method;
request.params = (api_params) ? api_params : [];
$.ajax({
type: "POST",
url: "http://localhost:8898/jsonrpc",
data: JSON.stringify(request),
timeout: 3000,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', window.btoa(options.username + ":" + options.password));
},
success: function(data) {
handleData(data, api_method);
},
error: function(jqXHR, textStatus, errorThrown) {
log("Connection time out: can't reach it. Try changing the settings.");
isConnected = "false";
},
dataType: "json"
});
}
var handleData = function(data, method) {
if (method == "getgenres") {
outputHTML = data.result.genres; //I need data.result.genres to return in getgenres function
}
}
var log = function(msg) {
if (options.debug == true) console.log(msg);
}
plugin.getgenres = function() {
APIcall("getgenres");
return outputHTML; //This is sadly empty.
}
};
}(jQuery));
var init = new $.lib();
console.log(init.getgenres());
I need that the getgenres function returns data.result.genres but actually it returns an empty array because getgenres is called for first and only after the handleData function gives to outputHTML the value that I need.
You are performing an asynchronous AJAX request, which means you can't actually get back the data immediately. There are two ways to solve your issue: making it synchronous (easy but ill advised) or using a callback (a little bit more complex but generally accepted):
In your getgenres function, you could accept one more parameter: callback
plugin.getgenres = function(callback) {
/* Dont forget APIcall already took two parameters in, so callback has to be the third in line! */
APIcall("getgenres", false, callback);
}
Now modify your APIcall function to accept your callback:
var APIcall = function(api_method, api_params, callback) { ... }
And call the callback from the successful completion call - instead of having a handler method in between wrapped in a function, you can simply pass the anonymous function. So instead of success: function(data){ handle(data); }, just use:
success: callback
The anonymous function that we will pass to it will receive as its first parameter the data you were passing to the handler. Now you can do the following:
var myGenres = [];
var init = new $.lib();
init.getgenres(function(data){
/* Now your data is actually loaded and available here. */
myGenres = data;
console.log(myGenres);
});
I would like to point out that there are many better ways to handle this, including turning this into a Constructor (More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) instead of the strange amalgamation of functions and variables you have now, as well as using JS Promises (here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to make this easier. But the basic gist should be here.
Update (potential implementation)
Because I mentioned that this could be done in a way that I think is clearer to read and use. I do not know all use cases for this, but from the provided example I would change the code to something looking like the following. Please also note I am not an expert on jQuery plugins, so I am avoiding plugging into jQuery and just using it as an easy AJAX call.
function getAjax(){
if(!window.jQuery || !window.$) throw("jQuery is required for this plugin to function.");
this.data = [];
this.request = '';
return this;
}
getAjax.prototype = {
createRequest : function(method, parameters){
this.request = {};
this.request.id = Math.floor((Math.random() * 100) + 1);
this.request.jsonrpc = '2.0';
this.request.method = method;
this.request.params = parameters || [];
return this;
},
callRequest : function(options, callback, error){
var self = this;
// We could also `throw` here as you need to set up a request before calling it.
if(!this.request) return this;
else {
$.ajax({
// We will allow passing a type and url using the options and use sensible defaults.
type: options.type || "POST",
url: options.url || "http://localhost:8898/jsonrpc",
// Here we use the request we made earlier.
data: JSON.stringify(this.request),
timeout: options.timeout || 3000,
beforeSend: function(xhr){
xhr.setRequestHeader(
'Authorization',
window.btoa( options.username + ":" + options.password)
);
},
// We will also store all the made request in this object. That could be useful later, but it's not necessary. After that, we call the callback.
success: function(data){
var store = {request:self.request, data: data};
self.data.push(store);
// Call the callback and bind `this` to it so we can use `this` to access potentially pther data. Also, pass the results as arguments.
callback(data, self.request.id).bind(self);
},
// Error function!
error: error,
dataType: options.dataType || "json"
});
}
return this;
}
}
// Example use
new getAjax().createRequest('getgenres').callRequest({
username: 'myusername',
password: 'mypassword'
}, function(data, id){
// Success! Do with your data what you want.
console.log(data);
}, function(e){
// Error!
alert('An error has occurred: ' + e.statusText);
console.log(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
What I do in those occasions is this:
You are supplying a method. So put a reference to the a callback function. In this case plugin.getGenresFinalize. When handleData is called it will fire that callBack function. This way you can pass multiple methods to the api call for different types of data.
plugin.getgenres = function() {
APIcall(this.getgenresFinalize);
}
plugin.getgenresFinalize = function(data) {
console.log(data);
}
var handleData = function(data, method) {
method(data);
}
I have a custom synchronization process where I queue up, in order, all of my sync records. When my service retrieves more than 1 sync record, it will process them, then update my last sync date for every successful record, or log my error when it fails (without updating the last sync date) and abort the sync process.
I've implemented the $q.all from AngularJS. Here's a subset of the sync loop:
var processes = [];
for (var i in data) {
if (data[i] === null || data[i].TableName == null || data[i].Query == null || data[i].Params == null) {
// Let's throw an error here...
throw new TypeError("ERROR! The data retrieved from the download sync process was of an unexpected type.");
}
var params = data[i].Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
mlog.Log("Query: " + data[i].Query);
mlog.Log("Params: " + paramsArray);
if (data[i].TableName === "table1") {
var process = $table1_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
},
null
);
processes.push(process);
} else if (data[i].TableName === "table2") {
var process = $table2_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
},
null
);
processes.push(process);
} else {
mlog.LogSync("WARNING! This table is not included in the sync process. You have an outdated version of the application. Table: " + data[i].TableName);
}
}
$q.all(processes)
.then(function (result) {
mlog.LogSync("---Finished syncing all records");
}, function (response) {
mlog.LogSync("Sync Failure - " + response, "ERROR");
});
Example ExecuteSyncItem function:
ExecuteSyncItem: function (script, params) {
window.logger.logIt("In the table1 ExecuteSyncItem function...");
var primaryKey = params[params.length - 1];
var deferred = $q.defer();
$DBService.ExecuteQuery(script, params,
function (insertId, rowsAffected, rows) {
window.logger.logIt("rowsAffected: " + rowsAffected.rowsAffected);
if (rowsAffected.rowsAffected <= 1) {
deferred.resolve();
} else {
deferred.resolve(errorMessage);
}
},
function (tx, error) {
deferred.reject("Failed to sync table1 record with primary key: " + primaryKey + "; Error: " + error.message);
}
);
return deferred.promise;
}
The problem I'm running into is, if there are more than 1 sync records that fail, then this line displays the same value for all records that failed (not sure if it's the first failure record, or the last).
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
How do I get it to display the information for the specific record that failed, instead of the same message "x" times?
As mentioned by comradburk wrapping your processes in a closure within a loop is a good solution, but there is an angular way in solving this problem. Instead of using the native for-in loop, you can do it via angular.forEach() and loop through all the data elements.
var processes = [];
angular.forEach(data, function(item) {
if (item === null || item.TableName == null || item.Query == null || item.Params == null) {
// Let's throw an error here...
throw new TypeError("ERROR! The data retrieved from the download sync process was of an unexpected type.");
}
var params = item.Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
mlog.Log("Query: " + item.Query);
mlog.Log("Params: " + paramsArray);
if (item.TableName === "table1") {
var process = $table1_DBContext.ExecuteSyncItem(item.Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
},
null
);
processes.push(process);
} else if (item.TableName === "table2") {
var process = $table2_DBContext.ExecuteSyncItem(item.Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
},
null
);
processes.push(process);
} else {
mlog.LogSync("WARNING! This table is not included in the sync process. You have an outdated version of the application. Table: " + item.TableName);
}
});
$q.all(processes)
.then(function (result) {
mlog.LogSync("---Finished syncing all records");
}, function (response) {
mlog.LogSync("Sync Failure - " + response, "ERROR");
});
The problem is due the closure you have on i. When the callback function executes, the value of i will be the last value in the for loop. You need to bind that value i to a separate, unchanging value. The easiest way to do that is with a self invoking function.
for (var i in data) {
(function(item) {
// Put your logic in here and use item instead of i, for example
mlog.LogSync("Error syncing record: " + response, "ERROR", data[item].Id
})(i);
}
Here's a good read for why closures cause this (it's a pretty common problem):
Javascript infamous Loop issue?
I have 3 ajax call in one function and checkAjaxCompletion which checks each ajax completion flag.
What the code below does is send multiple separate ajax calls and interval method checks completion flags to determine whether to proceed or keep interval. (I know clearInterval is not shown but the point is I want to use something other than interval)
Current code is:
function manyAjax() {
setInterval( function() { checkAjaxCompletion(); } , 200);
ajax1();
ajax2();
ajax3();
}
function ajax1() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function ajax2() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function ajax3() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function checkAjaxCompletion() {
if(ajax1_flag == 1 && ajax2_flag == 1 && ajax3_flag == 1) {
//everything went success, do some process
}
else if(ajax1_flag == 2 || ajax2_flag == 2 || ajax3_flag == 2) {
//some ajax failed, do some process
}
else {
//all ajax have not been completed so keep interval i.e. do nothing here
}
}
But I'm hesitating to depend on using interval function because calling it so often seem such waste of memory. There must be better way to do. I'm thinking if observer pattern can be applied here but would like to hear opinions.
It is observer-notifier, if you want to call it that - but each of your ajax calls will more than likely have a callback in javascript when they complete. Why not call checkAjaxCompletion() at the end of each of them, and do nothing if you're still waiting on others?
Dustin Diaz does a great job with this example.
function Observer() {
this.fns = [];
}
Observer.prototype = {
subscribe : function(fn) {
this.fns.push(fn);
},
unsubscribe : function(fn) {
this.fns = this.fns.filter(
function(el) {
if ( el !== fn ) {
return el;
}
}
);
},
fire : function(o, thisObj) {
var scope = thisObj || window;
this.fns.forEach(
function(el) {
el.call(scope, o);
}
);
}
};
The publisher:
var o = new Observer;
o.fire('here is my data');
The subscriber:
var fn = function() {
// my callback stuff
};
o.subscribe(fn);
To unsubscribe:
var fn = function() {
// my callback stuff
};
o.subscribe(fn);
// ajax callback
this.ajaxCallback = function(){
$.ajax({
type: "POST",
url: ajax.url,
data: {key: value},
async : !isAll,// false使用同步方式执行AJAX,true使用异步方式执行ajax
dataType: "json",
success: function(data){
if(data.status == 'successful'){
selfVal.parent().find('.msg').addClass('ok').html(msg.ok);
}else if(data.status == 'failed'){
checkRet = false;
selfVal.parent().find('.msg').removeClass('ok').html(msg.error);
}else{
checkRet = false;
}
return this;
}
});
}
return this;
Maybe you want to check your inputvalue callback ajax in your form;
You can view my website Demo, hope help you.
http://6yang.net/myjavascriptlib/regForm
Okay my idea was to make your own object that can handle sending an array of requests, keep a history of each request and do what i'm gonna call 'postProccessing' on each response, here is a probably very dodgy bit of code to hopefully demonstrate what I am thinking.
var Ajax = function() {
var request, callback, lst;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else if (window.ActiveXObject) {
request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.onreadystatechange = handleResponse;
this.history = [{}];
this.send = function(args) {
for (var i = 0; i < args.length; i++) {
if (args.url) {
request.open(args.type || 'GET', args.url);
}
request.send(args.data || null);
callback = args.callback;
lst++;
}
}
function handleResponse() {
var response = {
url: '',
success: true,
data: 'blah'
};
history.push(response);
if (postProccess()) {
callback();
}
}
function postProcess() {
if (this.history[lst].success) {
return true;
} else {
return false;
}
}
}