I have a series of nested Ajax requests to external APIs, which is very ugly but it was the only way I could figure out how to make calls in a specified order with each call utilizing some values brought back from the previous call. (I attempted this but couldn't get it to work, so I reverted to the advice here.)
Anyway, this works well to a point. All my calls work in succession, and I end up with an array called people, which is just a list of names: ["name1","name2","name3"].
My problem is that I don't seem to be able to do anything with this array from my javascript code. I can't append them to a div, nor can I alert them, or even console.log them during code execution. However, once my code completes, I can type people into the browser console and there they all are, as expected.
I am guessing this has something to do with the scope of the variable - I have tried making it global and moving the placement of its declaration, but the only way I can access people from the runnable code is from within the final AJAX loop, and then I get lots of repeating values because it's looping and adding to the array incrementally.
The goal here is to get people from that final API call and list them in HTML.
Here's my code. Any suggestions greatly appreciated.
HTML to trigger event:
<input type='file' accept='image/*' onchange='openFile(event)'>
<!--process is triggered by file upload-->
javascript:
var openFile = function(event) {
//... Some UI stuff happens here.
//... When finished, just call getGraph(); below
performances = new Array(); // global scope
people = new Array(); // global scope
getGraph(); // call function below
console.log(people); // retrieve array; doesn't work
};
function getGraph(){
$.ajax({
url:'http://...' + document.getElementById('prDate').value,
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
var programID = item.id;
$.ajax({
url:'http://...'+ programID',
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
performances.push( item.id );
});
$.each(performances, function(index, value){
$.ajax({
url:'http://...' + this.valueOf() +'/persons/',
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
people.push( item.firstname + ' ' + item.lastname ); // the magic moment
});
}
});
});
}
});
});
}
});
}
From your code it is visible that people variable will be create only once you call openfile function. If you want it be created even when the openfile method is not called then declare it outside of all the functions and then it will be accessible or else declare it in the place where you intend to use it like above the ajax call, then use it.
Have you tried putting it inside a IIFE closure ?
(function(){
var OpenFile = function() {
if ( !(this instanceof OpenFile) ) {
return new OpenFile();
}
var performances = new Array(); // closure Scope
var people = new Array(); // closure Scope
function getGraph(){
$.ajax({
url:'http://...' + document.getElementById('prDate').value,
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
var programID = item.id;
$.ajax({
url:'http://...'+ programID',
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
performances.push( item.id );
});
$.each(performances, function(index, value){
$.ajax({
url:'http://...' + this.valueOf() +'/persons/',
dataType:'json',
success: function(response){
$.each(response, function(i, item) {
people.push( item.firstname + ' ' + item.lastname ); // the magic moment
});
}
});
});
}
});
});
}
});
}
return {
get performances() : { return performances;},
get people() : { return people; },
getGraph : getGraph
};
};
window.OpenFile = OpenFile;
})();
which you can then call by doing something like
var myOpenFile = new OpenFile();
var people = myOpenFile.people;
myOpenFile.getGraph();
console.log(people);
with the added benefit that the OpenFile object is immediately available after the code loads. All the variables inside the code are only scoped to the object OpenFile and don't pollute the global namespace and you can choose what you wish to expose to others by putting them in the return statement at the end.
Related
In an HTML page, having two buttons (EDIT, DELETE), I do an AJAX call then I parse the returned JSON and I do perform an EDIT or a DELETE based on the returned ID. All this inside of a
$( document ).ready(function() {
...
$('button.icons8.icons8-edit-file').on("click", function(){
var key = $(this).attr('key');
$.ajax({
url: '/ede/' + key + '/json',
method: 'POST',
success: function (response) {
// PARSING THE JSON, EXTRACTING THE ID FROM IT
// AND PERFOMING THE EDIT
}
});
})
...
$('button.icons8.icons8-delete-file').on("click", function(){
var key = $(this).attr('key');
$.ajax({
url: '/ede/' + key + '/json',
method: 'POST',
success: function (response) {
// PARSING THE JSON, EXTRACTING THE ID FROM IT
// AND PERFOMING THE DELETE
}
});
})
...
});
What I would like to have is a global function what gets the key value, does the AJAX call and returns a value according to a pattern.
$( document ).ready(function() {
...
$('button.icons8.icons8-edit-file').on("click", function(){
var key = $(this).attr('key');
var value = GLOBAL_FUNCTION(key, pattern_edit);
// PERFOMING THE EDIT
})
...
$('button.icons8.icons8-delete-file').on("click", function(){
var key = $(this).attr('key');
var value = GLOBAL_FUNCTION(key, pattern_delete);
// PERFOMING THE DELETE
})
...
});
Where sould I put that GLOBAL_FUNCTION(p_key, p_pattern) fuction? Inside or outside of the ready function? How do I return the response from this global function? What if the result of parsing the response produces a list of values?
Where should I put that GLOBAL_FUNCTION(p_key, p_pattern) function? Inside or outside of the ready function?
You can put it anywhere in scope of all the places you want to call it. Global is one possibility but should be avoided where possible. Instead, I'd suggest namespacing your common functions in to their own object. This way you can then extract them to a separate JS file for easy re-use.
How do I return the response from this global function?
If the function is making an AJAX request you can't return anything, as the call should be asynchronous. You can work around this by providing a callback function to execute when the request completes.
With those points in mind, the code would look something like this:
// common.js
var common = (function() {
return {
global_function: function(key, callback) {
$.get('/ede/' + key + '/json', function(response) {
// perform common actions here, if needed..
callback && callback(response);
}
}
};
})();
// in your page:
$('button.icons8.icons8-edit-file').on("click", function() {
var key = $(this).data('key');
common.global_function(key, function(response) {
// Edit response.id...
});
});
$('button.icons8.icons8-delete-file').on("click", function() {
var key = $(this).data('key');
common.global_function(key, function(response) {
// Delete response.id...
});
});
It would also make sense to extract the delete and edit logic to the common library, but I'll leave that to you based on your own logic.
Finally, note the use of data() here, as creating your own non-standard attributes will make your HTML invalid. Use data-key instead.
You can put a function just in the jQuery ready state and use them in both callbacks. It's not really global, but it's in the right scope to use in both.
But GLOBAL_FUNCTION function can't return the value, because $.ajax is async. But you can pass a callback to the function, executed after finish.
And you can tell jQuery that the response is in JSON format, so it will parse it automatically for you.
$( document ).ready(function() {
function GLOBAL_FUNCTION(key, type, callback) {
$.ajax({
url: '/ede/' + key + '/json',
method: 'POST',
dataType: 'json,
success: function (response) {
// EXTRACTING THE ID FROM RESPONSE
var id = response.id;
if (type === 'edit') {
// PERFOMING THE EDIT
}
if (type === 'delte') {
// PERFOMING THE DELETE
}
// EXECUTE CALLBACK ON FINISH
callback(id, type);
}
});
};
$('button.icons8.icons8-edit-file').on("click", function(){
var key = $(this).attr('key');
GLOBAL_FUNCTION(key, 'edit', (id, type) => {
console.log(type + ' of id #' + id + ' finished');
});
})
$('button.icons8.icons8-delete-file').on("click", function(){
var key = $(this).attr('key');
GLOBAL_FUNCTION(key, 'delete', (id, type) => {
console.log(type + ' of id #' + id + ' finished');
});
})
});
On the jQuery AJAX success callback I want to loop over the results of the object. This is an example of how the response looks in Firebug.
[
{"TEST1":45,"TEST2":23,"TEST3":"DATA1"},
{"TEST1":46,"TEST2":24,"TEST3":"DATA2"},
{"TEST1":47,"TEST2":25,"TEST3":"DATA3"}
]
How can I loop over the results so that I would have access to each of the elements?
I have tried something like below but this does not seem to be working.
jQuery.each(data, function(index, itemData) {
// itemData.TEST1
// itemData.TEST2
// itemData.TEST3
});
you can remove the outer loop and replace this with data.data:
$.each(data.data, function(k, v) {
/// do stuff
});
You were close:
$.each(data, function() {
$.each(this, function(k, v) {
/// do stuff
});
});
You have an array of objects/maps so the outer loop iterates over those. The inner loop iterates over the properties on each object element.
You can also use the getJSON function:
$.getJSON('/your/script.php', function(data) {
$.each(data, function(index) {
alert(data[index].TEST1);
alert(data[index].TEST2);
});
});
This is really just a rewording of ifesdjeen's answer, but I thought it might be helpful to people.
If you use Fire Fox, just open up a console (use F12 key) and try out this:
var a = [
{"TEST1":45,"TEST2":23,"TEST3":"DATA1"},
{"TEST1":46,"TEST2":24,"TEST3":"DATA2"},
{"TEST1":47,"TEST2":25,"TEST3":"DATA3"}
];
$.each (a, function (bb) {
console.log (bb);
console.log (a[bb]);
console.log (a[bb].TEST1);
});
hope it helps
For anyone else stuck with this, it's probably not working because the ajax call is interpreting your returned data as text - i.e. it's not yet a JSON object.
You can convert it to a JSON object by manually using the parseJSON command or simply adding the dataType: 'json' property to your ajax call. e.g.
jQuery.ajax({
type: 'POST',
url: '<?php echo admin_url('admin-ajax.php'); ?>',
data: data,
dataType: 'json', // ** ensure you add this line **
success: function(data) {
jQuery.each(data, function(index, item) {
//now you can access properties using dot notation
});
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("some error");
}
});
Access the json array like you would any other array.
for(var i =0;i < itemData.length-1;i++)
{
var item = itemData[i];
alert(item.Test1 + item.Test2 + item.Test3);
}
This is what I came up with to easily view all data values:
var dataItems = "";
$.each(data, function (index, itemData) {
dataItems += index + ": " + itemData + "\n";
});
console.log(dataItems);
Try jQuery.map function, works pretty well with maps.
var mapArray = {
"lastName": "Last Name cannot be null!",
"email": "Email cannot be null!",
"firstName": "First Name cannot be null!"
};
$.map(mapArray, function(val, key) {
alert("Value is :" + val);
alert("key is :" + key);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
if you don't want alert, that is u want html, then do this
...
$.each(data, function(index) {
$("#pr_result").append(data[index].dbcolumn);
});
...
NOTE: use "append" not "html" else the last result is what you will be seeing on your html view
then your html code should look like this
...
<div id="pr_result"></div>
...
You can also style (add class) the div in the jquery before it renders as html
I use .map for foreach. For example
success: function(data) {
let dataItems = JSON.parse(data)
dataItems = dataItems.map((item) => {
return $(`<article>
<h2>${item.post_title}</h2>
<p>${item.post_excerpt}</p>
</article>`)
})
},
If you are using the short method of JQuery ajax call function as shown below, the returned data needs to be interpreted as a json object for you to be able to loop through.
$.get('url', function(data, statusText, xheader){
// your code within the success callback
var data = $.parseJSON(data);
$.each(data, function(i){
console.log(data[i]);
})
})
I am partial to ES2015 arrow function for finding values in an array
const result = data.find(x=> x.TEST1 === '46');
Checkout Array.prototype.find() HERE
$each will work.. Another option is jQuery Ajax Callback for array result
function displayResultForLog(result) {
if (result.hasOwnProperty("d")) {
result = result.d
}
if (result !== undefined && result != null) {
if (result.hasOwnProperty('length')) {
if (result.length >= 1) {
for (i = 0; i < result.length; i++) {
var sentDate = result[i];
}
} else {
$(requiredTable).append('Length is 0');
}
} else {
$(requiredTable).append('Length is not available.');
}
} else {
$(requiredTable).append('Result is null.');
}
}
I have this pretty long script which is right now using a second AJAX call to fetch the data as a temporary Workaround.
Where there suggestion call is made /admin/locations/suggest.json the result is an array of objects. A list is displayed with the data from these objects. When one of them is clicked a second AJAX call is made to /admin/locations/view/' + locationId + '.json to fetch the location data - again. This data is already there but in the data that was returned from the first AJAX call.
My issue is now accessing the Locations variable from inside the on.click() code. I've already got the index there and everything but locations doesn't have the data present.
How can I populate the locations after my first call and use them in the on.click() event?
SuggestLocation = function() {
var locations = null;
$('#NewLocation').hide();
function suggestLocation(locations) {
$.ajax({
url: '/admin/locations/suggest.json',
type: 'POST',
data: $("#AgendaItemAdminAddForm, #AgendaItemAdminEditForm").serialize(),
success: function (data) {
var htmlString = '';
for (var p in data.data) {
htmlString = htmlString + '<li data-index="' + p + '" data-id="' + data.data[p].Location.id + '">' + data.data[p].Location.display_name + '</li>';
}
$('#LocationSuggestions').html(htmlString);
locations = data.data;
console.log(locations);
},
dataType: 'json'
});
};
$(document).on('click', '#LocationSuggestions li', function(event) {
locationIndex = ($(this).data('index'));
locationId = $(this).data('id');
$.ajax({
url: '/admin/locations/view/' + locationId + '.json',
type: 'GET',
data: null,
success: function(data) {
$('#SelectedLocation').html(
Mustache.render($('#LocationTemplate').html(), data.data)
);
},
dataType: 'json'
});
$('#AgendaItemLocationId').val(locationId);
$('#AgendaItemLocationId').val(locationId);
$('#LocationFormFields').hide();
$('#LocationSuggestions').html('');
$('#NewLocation').show();
});
$('#LocationFormFields input, #LocationFormFields textarea').keydown(function() {
suggestLocation();
});
$('#LocationFormFields select').change(function () {
suggestLocation();
});
$('#NewLocation').click(function(event) {
event.preventDefault();
$('#AgendaItemLocationId').val($(this).data(''));
$('#LocationFormFields').show();
$('#SelectedLocation').hide();
suggestLocation();
$(this).hide();
return false;
});
};
SuggestLocation();
Where you have:
var locations = null;
creates locations in the outer scope (the assignment of null is redundant, it does nothing useful), however when you do:
function suggestLocation(locations) {
that creates a locations variable that is local to suggestLocation, so later when you do:
locations = data.data;
the data is assigned to that locations variable, not the outer one. None of the calls to suggestLocation pass a parameter to the function, so simply get rid of locations as a formal parameter, i.e. make it:
function suggestLocation() {
so the value is assigned to the outer locations that is available to all functions within SuggestLocation.
Just remember that the AJAX call is asynchronous so make sure the callback has been called and the value assigned before you try to access it.
Also, function names starting with a capital letter are, by convention, reserved for constructors so SuggestLocation is not appropriate. Nor is it a good idea to have two functions whose name is identical except for the capitalisation of a single letter.
I tried to use this loop to read some urls to read their modified time:
var arr = [];
//... fill arr with push
for (var e in arr) {
nodename=arr[e].hostname;
node_json="/nodes/"+nodename;
html +='data';
xhr = $.ajax({
url: node_json,
success: (function(nn) {
$('#host_'+nn).append("last modified: " + xhr.getResponseHeader("Last-Modified"));
})(nodename)
});
This already works a bit i I comment out the success line: I get calls to all node-files, and in Firebug, I can see the different modified times in the header of the calls.
At first I had a closure, (see How to generate event handlers with loop in Javascript?) And I only got the last line modified with all results. that's why I try to put the action in a separate function.
But that gives:
ReferenceError: xhr is not defined
$('#host_'+nn).append("last modified: " + xhr.getResponseHeader("Last-Modified")...
How do I get xhr into that function?
I aslo tried:
...
xhr[e] = $.ajax({
url: node_json,
success: add_result_to_info(nodename, e)
});
}
}
// outside loop
function add_result_to_info(nn, e) {
$('#host_'+nn).append("last modified: " + xhr[e].getResponseHeader("Last-Modified"));
}
source of the AJAX call: Get the modified timestamp of a file with javascript
If arr is truly an array, just use .forEach or even better .map (with a shim on older browsers) to encapsulate each iteration's scope without the need for additional closures:
var xhrs = arr.map(function(e) {
var nodename = e.hostname;
var node_json = "/nodes/" + nodename;
html +='data';
return $.ajax({
url: node_json
}).done(function(data, status, xhr) {
$('#host_'+nodename).append("last modified: " + xhr.getResponseHeader("Last-Modified"));
});
});
The reason to use var xhrs = arr.map() instead of .forEach is that you then (for free) get the ability to call yet another callback once every AJAX request has completed:
$.when.apply($, xhrs).then(function() {
// woot! They all finished
...
});
your are directly executing the method and passing its result as the callback for the success callback.
the xhr is already passed as the 3rd argument so try
success: function(nn,status, xhr) {
$('#host_'+nn).append("last modified: " + xhr.getResponseHeader("Last-Modified"));
}
if you have to pass the nodename as well, the you need to use a function that returns a function
success: (function(nn){
return function(data ,status, xhr) {
// you can use nodename here...
$('#host_'+nn).append("last modified: " + xhr.getResponseHeader("Last-Modified"));
};
})(nodename)
I've been building onto some example code for the Twitter Bootstrap Typeahead plugin.
In an early development version of the script, I included the following, lifted almost directly from the example, with a few customisations that have worked perfectly;
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
var data = [{"buildingNumber":"1","buildingDescription":"Building One"},{"buildingNumber":"2","buildingDescription":"Building Two"},{"buildingNumber":"3","buildingDescription":"Building Three"}];
$.each(data, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
In practice, this isn't much use while I've got the array of options written directly into the code, so I've been looking at reading an external file with the JSON written in. I've created a file, containing just the array as follows;
[{"buildingNumber":"1","buildingDescription":"Building One"},
{"buildingNumber":"2","buildingDescription":"Building Two"},
{"buildingNumber":"3","buildingDescription":"Building Three"}]
And I've now attempted to update the Javascript to include the code to load up the remote file. I can verify the file exists and is in the correct relative location.
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
var data = function () {
$.ajax({
'async': false,
'global': false,
'url': "../json/buildings",
'dataType': "json",
'success': function (result) {
data = result;
}
});
return data;
}();
$.each(data, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
On running the page, all of the elements appear to work as expected, and nothing appears in the Console, until you click inside the text field and being typing. After each keypress, nothing visibly happens, but the following is produced in the Console;
Uncaught TypeError: Cannot read property 'length' of undefined [jquery.min.js:3]
Any ideas/thoughts/starting points to try and fix this would be much appreciated!
First of all, I would recommend you to use $.getJSON instead of $.ajax (you can save a lot of unnecessary lines of code). // See $.getJSON doc here: http://api.jquery.com/jQuery.getJSON/
Second, you have to reference the data variable according to its scope (when calling data var inside the success function, the scope has changed and the data var is not found, that is why it's throwing "Cannot read 'leangth' of undefined"). You have to set a self reference variable that points to the data variable scope.
This will help:
$('.building_selector').typeahead({
source: function (query, process) {
var buildings = [];
var map = {};
var self = this; //This is a self reference to use in the nested function of $.getJSON
$.getJSON('../json/buildings', function(data){
if ($.isArray(data)) {
$.each(data, function (i, building) {
self.map[building.buildingDescription] = building;
self.buildings.push(building.buildingDescription);
});
process(self.buildings);
}
});
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber; // This won't work. I'd suggest to move the map variable as part of the top level object.
return item;
}
});
I shall add a little explaination of the point I reached in the end, as it's quite a change;
As the content of the JSON file is dynamic, but doesn't need to be called on every keypress, I decided to import it once, using $.getJSON inside a $document.ready(). It then writes the content into a global variable, which can be loaded by the source function exactly as before.
Here's the code for reference;
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
$.each(buildinglist, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
var buildingList;
$(document).ready(function() {
$.getJSON('../json/buildings/', function(json){
buildinglist = json;
});
});