Getting array values from multidimensional array in JavaScript - javascript

I'm in need of some minor assistance. I'm having trouble getting an array (larray3) populated with two other array objects (larray1 and larray2) to pass both from data.js into the subsequent model.js and view.js. Data.js correctly builds the multidimensional array however when the results are received in model.js/view.js I only receive the results for larray1. Because only the first values come thru I cannot tell if both larray1 and larray2 are actually passing thru. Can someone please tell me how I should alter my syntax in either model.js or view.js to access both array values or what else I could change? Thank you in advance.
data.js.
function getCountries(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var larray1 = [];
var larray2 = [];
var larray3 = [];
db.all('SELECT * FROM Country', function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray1.push(row.CountryName);
larray2.push(row.CountryCode);
})
larray3.push(larray1);
larray3.push(larray2);
return done(larray3[0], larray3[1]);
});
db.close();
}
model.js
function Countries(done) {
//Pull location values from data
return getCountries(done);
}
view.js
function viewCountries() {
var viewCou = Countries(function(results) {
// Code only gets triggered when Countries() calls return done(...);
var container = document.getElementById('country-select');
var fragment = document.createDocumentFragment();
results.forEach(function(loc, index) {
var opt = document.createElement('option');
opt.innerHTML = loc;
opt.value = loc;
fragment.appendChild(opt);
});
container.appendChild(fragment);
})
}

In data.js you send two arguments to the done callback:
return done(larray3[0], larray3[1]);
This done function is passed through in your model.js:
return getCountries(done);
And that done is passed in from view.js:
Countries(function(results) { // ...
So it is this anonymous function (function(results) {...}) that is called in data.js. But notice that this function only has one parameter, so you're doing nothing with the second argument that data.js sends. result gets the value of larray3[0], but larray3[1] is not captured anywhere.
You could solve this in different ways. Personally, I think the design with two arrays is wrong from the start. I would not separate data that belongs in pairs (name and code) into two different arrays.
Instead make an array of objects, and pass that single array around:
In data.js:
rows.forEach(function(row) {
larray1.push({
name: row.CountryName,
code: row.CountryCode
});
})
return done(larray1);
In view.js:
opt.textContent = loc.name;
opt.value = loc.code;
Side-note: .textContent is preferred over .innerHTML when assigning plain text.

Related

How can I run a callback after a loop of Promises?

Specifically, given a list of data, I want to loop over that list and do a fetch for each element of that data before I combine it all afterward. The thing is, as written, the code iterates through the entire list immediately, starting all the operations at once. Then, even though the fetch operations are still running, the then call I have after all that runs, before the data could've been processed.
I read something about putting all the Promises in an array, then passing that array to a Promise.all() call, followed by a then that will have access to all that processed data as intended, but I'm not sure how exactly to go about doing it in this case, since I have nested Promises in this for loop.
for(var i in repoData) {
var repoName = repoData[i].name;
var repoUrl = repoData[i].url;
(function(name, url) {
Promise.all([fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/pulls`)])
.then(function(results) {
Promise.all([results[0].json(), results[1].json()])
.then(function(json) {
//console.log(json[0]);
var commits = json[0];
var pulls = json[1];
var repo = {};
repo.name = name;
repo.url = url;
repo.commitCount = commits.length;
repo.pullRequestCount = pulls.length;
console.log(repo);
user.repositories.push(repo);
});
});
})(repoName, repoUrl);
}
}).then(function() {
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});
Generally when you need to run asynchronous operations for all of the items in an array, the answer is to use Promise.all(arr.map(...)) and this case appears to be no exception.
Also remember that you need to return values in your then callbacks in order to pass values on to the next then (or to the Promise.all aggregating everything).
When faced with a complex situation, it helps to break it down into smaller pieces. In this case, you can isolate the code to query data for a single repo into its own function. Once you've done that, the code to query data for all of them boils down to:
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
}))
Please try the following:
// function to query details for a single repo
function getDataForRepo(username, repoInfo) {
return Promise
.all([
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/pulls`)
])
.then(function (results) {
return Promise.all([results[0].json(), results[1].json()])
})
.then(function (json) {
var commits = json[0];
var pulls = json[1];
var repo = {
name: repoInfo.name,
url: repoInfo.url,
commitCount: commits.length,
pullRequestCount: pulls.length
};
console.log(repo);
return repo;
});
}
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
})).then(function (retrievedRepoData) {
console.log(retrievedRepoData);
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});

How to save the result from collection.findone()

i have a simple question and i have read a lot of same issues here, but these are not exact the same or doesn't work for me :-(
I have a REST function called "addevent". The function gets a json input (req) and iterate through the json array to get some IDs to store them in an extra Array. That works perfect!
After that, the function should search in a mongodb for every single id and store some extra informations from this ID (e.g. the stored URL of this ID). With "console.log(result.link)" it works again perfect. But my problem is that, that i need to store this link in an extra Array (urlArray).
So how can i save the result of collection.findone(). I read something about, that findone() doesn't return a document, but a cursor? what does that mean? How do i have to handle that in my case?
That's the code:
exports.addevent = function(req, res) {
var ids = req.body;
var pArray = new Array();
var urlArray = new Array();
var eventName = ids.name;
for(var i in ids.photos) {
photoArray.push(ids.photos[i]);
var id = ids.photos[i]._id;
var collection = db.get().collection('photos');
collection.findOne({'_id':new mongo.ObjectID(id)},function(err, result) {
console.log(result.link);
}
)
}
Many thanks!
-------------------- Update --------------------
Ok, i think that has something to do with the asynch Callbacks. I found an article, but i don't know how to implement it in my case.
http://tobyho.com/2011/11/02/callbacks-in-loops/
And something about "promises" in javascript.
You can save the result of your search doing something like:
var foundPhoto = collection.find({_id':new mongo.ObjectID(id)}, function(err, photo){
if(!err){
return photo;
} else {
console.log(err)
return null;
}
});
This way you get the return statement of your query in the "photo" variable.

Passing JavaScript select value between forms

I'm in need of some expert JavaScript advice. I'm coding using Electron.
The issue: I'm trying to capture the value selected from the second of two dropdown lists and pass it back into a JavaScript file. The code below is ordered as it would run. The dropdown code is not shown as it is simply populated by the viewProvinces function below. The first dropdown's id is "country-select" while the second is "province-select".
In this case, a link is clicked in Index.html which calls the anonymous function in Data.js. The anonymous function calls viewProvinces that populates the parray/data variables from the anonymous function which produces the alert showing the value returned.
(FYI) viewProvinces also populates the second dropdown (id province-select) by filtering based on the values produced in the first dropdown (id country-select). For example, if Afghanistan is selected as a country in the first, then only provinces from Afghanistan are shown in the second.
Moving on, viewProvinces calls Provinces which is an array populated when it calls getProvinces after querying a SQLite database for the source data.
ViewProvinces, Provinces, and getProvinces all work correctly. The link and the anonymous function are the issue and technically work in that they produce a result, but not the correct result. When the link is clicked it produces "object Object". I believe I understand why it is doing this, though I am not skilled enough (yet) to know how to fix it. I do not know how to adjust the code so that it returns the actual value(s) selected from the second (provinces) dropdown.
Put simply, the data is gathered from a SQL query that populates a series of arrays that further populates the dropdown list. The value of this list, once selected, should be returned back to the source JavaScript file into a variable (it fails here).
Apologies if this sounds convoluted, but I'm trying to be thorough. Any help in this matter would be greatly appreciated! Thank you!!
Index.html:
<a id="test-select" href="#">test</a>
Data.js:
$( "#test-select" ).click(function(e) {
e.preventDefault();
var parray = viewProvinces($("#country-select").val());
var data = $('#test-select').data(parray);
alert(data);
});
View.js:
function viewProvinces(ccode) {
var viewPro = Provinces(function(results) {
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
});
container.appendChild(fragment);
});
}
Model.js:
function Provinces(done) {
//Pull location values from data
return getProvinces(done);
}
Data.js:
function getProvinces(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var stmt = 'SELECT Country.CountryId, Country.CountryCode, Country.CountryName, Province.ProvinceName, Province.ProvinceCode FROM Province INNER JOIN Country ON Province.CountryId = Country.CountryId'
var larray = [];
db.all(stmt, function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray.push({
ccode: row.CountryCode,
cname: row.CountryName,
pname: row.ProvinceName,
pcode: row.ProvinceCode
});
})
return done(larray);
});
db.close();
}
I have tried to answer your question via a fiddle based on your code created here but I had some trouble understanding a couple of things in your code, so I might have missed something. The main change I made was to make the Provinces(function(results) {.. function return the array of filtered provinces:
function viewProvinces(ccode) {
return Provinces(function(results) {
var provinces = [];
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
provinces.push(loc);
});
container.appendChild(fragment);
return provinces;
});
Once this is done, the parray is correctly setup:
var parray = viewProvinces($("#country-select").val());
However, I was confused when I read this code:
var data = $('#test-select').data(parray);
alert(data);
I assumed you were trying to save the provinces data in the link's store, so modified the code as follows to demo that it works:
$('#test-select').data({
provinces: parray
}); // Save the provinces array
var data = $('#test-select').data(); // Retrieve the provinces array
//Dump it on the console
$.each(data.provinces, function(index, province) {
console.log("Province[" + index + "].name: " + province.cname);
});

RSVP.js - Multiple asynchronous function calls on array

I have a result from a REST call that contains a list of files. Each file has properties that I have to extract and place in a new array. This is straightforward and is easily done with a simple loop. Three of the properties I need to extract are accessible in a direct way, while three other properties are of HATEOAS type, which in other words mean that for each file in the result, I have to make three other asynchronous calls to retrieve it's values.
My first instinct was to use RSVP.all() to process my promises and to map the items in the new array to the corresponding properties in the original list of files using map, but I cannot figure out how to achieve this.
I want to achieve something like below, but I have no idea how I can get the index of the current mapped item in itemList, to include the correct file from fileList. How can I do this?
On a sidenote, if I use RSVP.all() the wrong way I'm happy to receive tips!
function createItemList(fileList) {
var promise = new RSVP.Promise(function(resolve, reject) {
var itemList = [];
//For each file in fileList, get the directly accessible properties
//and push it to a new array
for (var i = 0, file; file = fileList[i]; i++) {
currentItem.Name = file.Name;
currentItem.LastModified = new Date(file.TimeLastModified).format("dd-MM-yyyy hh:mm:ss");
currentItem.Version = file.MajorVersion + "." + file.MinorVersion;
itemList.push(currentItem);
}
//This is where it starts to get messy...
//I want to map each item in the new itemlist to the corresponding properties
//in the fileList. If I can include the corresponding file somehow, I could set the
//data in the method 'getModifiedBy' and similar. I believe this should work,
//but I have no idea how I can achieve this.
var modifiedBy = itemList.map(function(item) {
return getModifiedBy(item, fileList[INDEX]);
});
var checkedOutBy = itemList.map(function (item) {
return getCheckedOutBy(item, fileList[INDEX]);
});
var eventDate = itemList.map(function (item) {
return getEventDate(item, fileList[INDEX]);
});
var promises = {
promisesModifiedBy: modifiedBy,
promisesCheckedOutBy: checkedOutBy,
promisesEventDate: eventDate
};
RSVP.all(promises)
.then(function() {
resolve(itemList);
});
});
return promise;
}
Use only a single map over the itemlist that returns a Promise for your 3-property-object. Use a dedicated helper function for single items.
Btw, with new RSVP.Promise you're using the deferred antipattern while there's absolutely no reason - RSVP.all() already returns you the result promise.
function createItemList(fileList) {
return RSVP.all(fileList.map(createItem));
}
function createItem(file) {
// get the directly accessible properties
var currentItem = {};
currentItem.Name = file.Name;
currentItem.LastModified = new Date(file.TimeLastModified).format("dd-MM-yyyy hh:mm:ss");
currentItem.Version = file.MajorVersion + "." + file.MinorVersion;
return RSVP.hash({
modifiedBy: getModifiedBy(currentItem, file),
checkedOutBy: getCheckedOutBy(currentItem, file)
eventDate: getEventDate(currentItem, file)
}).then(function(asyncprops) {
// somehow mix the asyncprops with the currentItem
return item; // into a result item
});
}

return value as key on map reduce

Currently my views on couchdb always return something like this:
{
"key":"somekey",
"value":"somevalue"
}
I wonder if there's any way to return something like this:
{
"somekey":"somevalue"
}
i need to access "somekey" directly without having to search the whole array for a key with "somekey" value on it.
Thanks
When using a view, there is no direct way provided by CouchDB to return the collection of documents as anything but key/value as you've seen.
You'd need to map the data on the client side into a keyed index, or request the document by the key directly from the view:
/sample/_design/docs/_view/by_somekey?key="somekey"
If you're looking for a fast map option in JavaScript (avoiding calling a function for each iteration), you could just use this simple logic of course:
// docs are the documents returned from the view
var map={};
for(var i=0,len=docs.length;i<len;i++) {
map[docs[i].key] = docs[i].value;
}
You can do this with underscoreJS (http://underscorejs.org/#reduce).
var map = {
"key":"somekey",
"value":"somevalue"
};
var result = _.reduce(map, function(key, value){
var result = {};
result[key] = value
return result;
});
alert(result.somekey);
http://jsfiddle.net/BcYtw/1/
If you do not like the format or data for Couch queries, you can reformat it at your will with list function. Something like this should work for you:
function(head, req){
send('[');
row = getRow();
if (row){
// here you make your own JS structure to be returned,
// serialize it with toJSON() and send to client with send()
// First row:
var new_row = {};
new_row[row.key] = row.value;
send(toJSON(new_row));
while(row = getRow()){
// Next rows:
send(',');
var new_row = {};
new_row[row.key] = row.value;
send(toJSON(new_row));
}
}
return ']';
}

Categories