I am working on a JavaScript web app that takes input from a user (the name of a musical artist) and outputs a list of related artists and their most popular song, as determined by the Spotify API. I initially had a rudimentary version of this functioning, but it would just post a list of all the related artists with a list of those related artists' most popular songs immediately above it, and I want the entire object to print out (artist plus most popular song).
I have a series of objects representing the artists that I received through the spotify-web-api-js node module with Node.js and Browserify to make it function on the browser, uniquely identified by their Spotify ID. How can I loop through them if I don't know those IDs in advance before a user does a search so that I can properly push them into an object that I can then input to the DOM through jQuery's append? I've been trying to access them in various ways, but it doesn't seem to be working:
s.getArtistRelatedArtists(originalArtistId, function(err, data) {
for (i = 0; i < data.artists.length; i++)
{
console.log(data.artists[i].name);
relatedArtistsObject[data.artists[i].id] = {
name: data.artists[i].name,
songs: []
};
s.getArtistTopTracks(data.artists[i].id, "US", function (err, data2) {
if (relatedArtistsObject[data2.tracks[0].artists[0].id] !== undefined)
{
// console.log(data2.tracks[0].name); -- this outputs the song titles I want
relatedArtistsObject[data2.tracks[0].artists[0].id].songs.push(data2.tracks[0].name);
}
});
}
console.log(relatedArtistsObject);
// loop through this object and print it through the screen
for (k = 0; k < relatedArtistsObject.length; k++)
{
console.log(relatedArtistsObject.id.songs[0].name);
$('#related-artist').append(relatedArtistsObject[k]);
}
// $('#related-artist').append(relatedArtistsObject);
});
Here is a link to the full code (not functioning because it doesn't have Node.js/browserify enabled): https://jsfiddle.net/pmh04e99/
This answer is partially helpful, but doesn't apply here because the JSON output doesn't have a "name" field in the array I want to access. Instead, songs is an array of 1, and the content I want is within item 0. My console.log output of the relatedArtistsObject looks like this, for example:
How can I access these objects in the DOM through my code when I don't know the spotify IDs right now?
Note: I'm aware that i'm not error handling yet, but I want to be able to implement the main functionality first.
You can use for in or use Object.keys.
The former will iterate over each enumerable property name of the object, and setting that name into the specified variable. You can then use that name to access that object's property value.
The latter will return an array of the object's property names. You can then use a regular for loop to iterate over that array and use each of the values in the array as the id
For...In
for(id in relatedArtistsObject){
var artist = relatedArtistsObject[id];
console.log(artist.name,artist.songs);
}
Object.keys
var ids = Object.keys(relatedArtistsObject);
for(var i=0; i<ids.length; i++){
var artist = relatedArtistsObject[ids[i]];
console.log(artist.name,artist.songs);
}
Related
I currently save a bunch of objects (thousands) into the chrome.storage.local and then when on a specific web page checking whether specific IDs on the web page are in fact saved in local storage.
Here's a pseudo code
Bakcground script:
var storage = chrome.storage.local;
var json = '[{"kek1": {"aaa": "aaaValue", "bbb": "bbbValue", "ccc": "cccValue"}},{"kek2": {"ddd": "dddValue", "eee": "eeeValue", "fff": "fffValue"}}]';
var jsonParsed = JSON.parse(json);
jsonParsed.forEach(function(object) {
storage.set(object);
});
Content script (when on a specific page):
ids.forEach(function(id) {
storage.get(id, function(result){
if(!isEmpty(result)) {
//we found it, nice, now flag it as found
}
});
});
function isEmpty(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
Which is easy and nice since I only have to do storage.get(id, ...
Unfortunately, I save a lot of stuff in storage, some of it I need to be removing periodically, which then becomes a hustle since I have to loop through all the objects and determining whether that particular object needs to be removed or it needs to remain.
So i decided I would do like these "parent object". Ie one object for settings, containing an array of objects with different settings the user would save. One object for the stuff that needs to be removed, containing an array objects. Etc
Like so - all relevant info that I want to remove periodically will be under one key "test" (temp name):
var json = '{"test":[{"kek1": {"aaa": "aaaValue", "bbb": "bbbValue", "ccc": "cccValue"}},{"kek2": {"ddd": "dddValue", "eee": "eeeValue", "fff": "fffValue"}}]}';
I know how to access the nested objects and their values:
var jsonParsed = JSON.parse(json);
jsonParsed.test[0].kek1.aaa
But I don't know how I would easily check for the keys saved in the storage since I would have to specify the "element number" ([i]).
Do I just do a for loop itterating over the array like so?
for (i = 0; i < jsonParsed.test.length; i++) {
var getKey = Object.keys(jsonParsed.test[i]);
if (getKey[0] == 'theKeyImLookingFor') {
//do stuff
}
}
To me that feels like non ideal solution since the for loop would have to run for each of the ids on the page and there could sometimes be close to 4000 of them. (4000 for loops back to back)
Is it a good idea to save a single object holding an array of thousands of other objects?
Am I doing it wrong or is this the way to go?
But I don't know how I would easily check for the keys saved in the storage
Use the standard Array methods like find or findIndex:
const i = arrayOfObjects.findIndex(o => 'someKey' in o);
Is it a good idea to save a single object holding an array of thousands of other objects?
It's a bad idea performance-wise.
What you probably need here is an additional value in the storage that would contain an array with ids of other values in the storage that need to be processed in some fashion e.g. expired/removed. It's basically like a database index so you would update it every time when writing an individual object. Since it contains only the ids, updating it is cheaper than rewriting the entire data.
Also, instead of performing lots of calls to the API, do just a single call:
// writing
chrome.storage.local.set(Object.assign({}, ...arrayOfObjects));
// reading
chrome.storage.local.get(arrayOfIds, data => {
for (const id of arrayOfIds) {
const value = data[id];
if (value !== undefined) {
// ok
}
}
});
I am following ag-grid's official tutorial:
https://www.ag-grid.com/angular-getting-started/?utm_source=ag-grid-readme&utm_medium=repository&utm_campaign=github
I reached the part where I have to manipulate the information regarding the selected checkboxes. However, the documentation is not detailed; It does not explain how the code actually works. Perhaps, this makes sense since it is not their job to explain in detail. However, for someone like me who doesn't have solid experience with working with angular technology and who wants to truly understand how things work, this is troublesome!
In the html file, I have been asked to add this:
<button (click)="getSelectedRows()">Get Selected Rows</button>
And in app.component.ts, I have been asked to add this:
getSelectedRows() {
const selectedNodes = this.agGrid.api.getSelectedNodes();
const selectedData = selectedNodes.map(node => node.data);
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
alert('Selected nodes: ${selectedDataStringPresentation}');
}
If someone could explain what the typescript code is doing exactly, that would be very generous.
Thank you!
I guess agGrid is the service storing your mock values, this simply gets an array of data from somwhere.
selectedData is another array that is created by transforming (transforms the array while providing a new reference, thus not modifying the original array) the selectedNodes array and only selecting the data property of each node.
selectedDataStringPresentation is the same, but this time it provides an array of formatted strings by merging the properties make and model of each object from selectedData.
What you probably fail to grasp is the usage of the ES6 (JavaScript standard) functions that are used here, and especially the map() function.
Here is the official documentation of the map() function for arrays : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Simply explained, it is a function that iterates over an array, and transforming each object by applying the function declared in the map, returning the results in a new array.
If you take the example of selectedData, you can translate the operation into this :
Loop over each object of selectedNodes, and return only the property data of the current object. Push the results in a new array.
This is especially useful when you want to work on different arrays that serve different purposes. For example, imagine you have a service that contains a list of objects. A backend service will provide you an array of numbers representing the IDs of the objects you have in your service.
You can then use the map() function to transform the array of IDs into an array of your Objects stored in your service easily.
Darn #Alex Beugnet(upvote) beat me to the punch! I'm going to post anyway as I was in the middle of writing it all.
Firstly I'm not sure how much of TypeScript you already know, I apologize if much of these becomes trivial, the purpose is only to ensure maximum clarification to the question in understanding the logic.
In the Enable Selection portion of the guide, you are essentially enabling multiple row selection in the grid and having the button return the data from those selected rows.
In order to see what's happening with the getMultipleRows() function, it would be best to visualize it via the Debugger provided in browsers, I'm using Chrome Developer Tools (hit F12), I would highly recommend it for understanding what is happening in the logic.
const selectedNodes
Let's start by selecting say 2 rows, I have selected the Porsche Boxster 72000 and Ford Mondeo 32000. After selecting them I click on the 'Get Selected Rows' button which triggers the getSelectedRows() function:
const selectedNodes = this.agGrid.api.getSelectedNodes();
The above line is assigning the constant variable 'selectedNodes' the RowNodes from AgGrid. Here you are using the AgGridNg2 method getSelectedNodes() to return the selected node data, which you would be returned an array in the form of:
[RowNode, RowNode] //(each for the row we have selected)
Looking into a RowNode we get:
These are all the RowNode properties provided by the AgGrid framework, you can ignore all of these object properties as you are only concerned with the 'data' property as you'll see in the next line of code!
const SelectedData
const selectedData = selectedNodes.map(node => node.data);
Here we are setting 'selectedData' as an array of RowNode.data, basically trying to get the data property from the RowNodes into an array.
The above line can basically be assumed as:
let selectedData = [];
for (let i = 0; i <= selectedNodes.length - 1; i++){
selectedData[i] = selectedNodes[i].data;
}
In which we are just trying to get the data property into a new constant variable 'selectedData'. Look at the documentation in order to better understand this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const selectedData would be returned as:
[
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
const selectedDataStringPresentation
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
We take the selectedData array and concatenate the Make and Model as a single element for the array and adding a comma in the end. We would get "Porsche Boxter, Ford Mondeo".
let selectedData = [
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
let selectedDataStringPresentation = [];
for (let i = 0; i <= selectedData.length - 1; i++){
selectedDataStringPresentation[i] = selectedData[i].make + ' ' + selectedData[i].model;
}
selectedDataStringPresentation = selectedDataStringPresentation.join(', ');
console.log(selectedDataStringPresentation);
alert()
And the last line,
alert('Selected nodes: ${selectedDataStringPresentation}');
You are going to send an alert in the browser that will display the selectedDataStringPresentation array.
I have two data sets that vary in length. I need to loop through both arrays and push the photo url of the matching player ID's to a new array. I am doing this after the API data has loaded into my React component, so it does not seem to be a matter of the data not being loaded yet. I can console.log both data sets and see that they are successfully being returned from the API. All of this works in JS Fiddle (link below), but not React. So I am having a tough time tracking down what is going on.
Here are my data sets (all of them have obviously been shortened):
This object array is returned from an API endpoint that holds all of the player information, such as ID, Photo URL's, etc etc. (this is 1200+ objects):
playerInfo = [
{PlayerID: 123, PhotoURL: url123},
{PlayerID: 345, PhotoURL: url345},
{PlayerID: 678, PhotoURL: url678},
{PlayerID: 910, PhotoURL: url910},
{PlayerID: 1112, PhotoURL: "url1112"},
{PlayerID: 1213, PhotoURL: "url1213"}
];
This data is returned from a separate API endpoint that lists the current leaders (returns top 40 leaders):
playerIDs = [123, 345, 678, 910]
Unfortunately this leaderboard data does not include the photo URL's with the returned player ID's, so I have to compare the player IDs in both arrays, and then push the PhotoURL from the above matching playerInfo array.
My approach is as follows(I probably could be using some type of ES6 feature for this, so please let me know if that's the case.):
let leaderPhotos = [];
for(let i = 0; i < playerInfo.length; i++) {
if(playerInfo[i].PlayerID === playerIDs[i]) {
leaderPhotos.push(playerInfo[i].PhotoURL);
}
}
My thinking for this is that even though the playerIDs array is shorter than the PlayerInfo array, setting the loop to the length of the PlayerInfo array will allow the shorter PlayerIDs leaderboard array to be compared against every player ID in the PlayerInfo object array since any player could be on the leaderboard at any given time. If a Player ID matches within both arrays, it pushes the photo URL value of the PlayerInfo object to an array, that I can then use to load in top leader photos. Since the loop goes in order through the leaderboard PlayerIDs, the returned photo URL's are in order of the leader board.
Here it is working in JS Fiddle.
You can see in the console that it only pushes the photo URL's of the matching player ID's. Perfect, just what I want.
However when this same thing is applied inside of the React component responsible for handling the API data, I am getting back an empty array in the console after the logic has run it's course. I can console.log both of the returned API data sets to see that I am successfully getting data back to work with.
I was messing around, and decided to see if setting both array indexs to a value would work like so:
let leaderPhotos = [];
if(playerInfo[0].PlayerID === playerIDs[0]) {
leaderPhotos.push(playerInfo[0].PhotoURL);
} else {
return false;
} console.log(leaderPhotos);
And sure enough, this will push the matching pair's photo url to leaderPhotos as expected without the for loop. I am having a difficult time understanding why the for loop is not working, even though in JS Fiddle it proves that this should return the desired results. Thanks for any tips in advance!
Short clean version
const leaderPhotos = playerIDs.map(id =>
playerInfo.find(({ PlayerID }) => PlayerID === id).PhotoURL
);
Note: Just to let you know this is a nested loop. Where map is a loop through the leadership board and find is a loop through the players. The time complexity is O(n * m) where n is the total users and m is the length of the leadership board.
Though if the total number of users isn't huge it shouldn't matter and as long as the code looks clean and understandable that would always be better (:
If you have a HUGE number of users and you want to have the most efficient code. What you should do is store your playerInfo in a map/object where the key is the playerId and the value is the playerURL. This way it only needs to loop through your objects once and has a O(1) retrieval for the player photos.
Efficient Version
const playerMap = {};
playerInfo.forEach((player) => {
playerMap[player.PlayerID] = player.PhotoURL;
});
const leaderPhotos = playerIDs.map(leaderId => playerMap[leaderId]);
The time complexity of this is at most O(n) where n is the number of players.
Try this
let leaderPhotos = playerInfo.filter(plr => playerIDs.indexOf(plr.PlayerID) !== -1).map( t => t.PhotoURL);
Make sure that both of your arrays have the same order based on playerId.
Otherwise, you need to use two loops:
let leaderPhotos = [];
for(let i = 0; i < playerInfo.length; i++) {
for(let j = 0; j < playerIDs.length; j++ )
if(playerInfo[i].PlayerID === playerIDs[j]) {
leaderPhotos.push(playerInfo[i].PhotoURL);
}
}
I think so this is the shortest solution possible compared to the other answers:
let leaderPhotos = playerInfo.filter((info) => playerIDs.includes(info.PlayerID)).map(id => id.PhotoURL);
I'm retrieving data from
https://api.instagram.com/v1/tags/HASHTAG/media/recent?client_id=CLIENT_ID
whereHASHTAG = the hashtag I'm searchingCLIENT_ID = my client idI am having no trouble retrieving the picture urls and captions but when I try to get the comments the comments are being returned as 'undefined'.The data is stored in an array called pictures so if I want to get the standard resolution url I just do:
for(i = 0; i < pictures.length; i++){
pictureURL[i] = pictures[i].images.standard_resolution.url;
}
Right now the code I'm trying to use to retrieve the comments is :
//where 'i' is the index of the pic I'm currently focusing on
for (comment in pictures[i].comments.data) {
alert(comment.text);
//using alert just to see what is being retrieved
}
But the issue is that the alerts are only displaying 'undefined' and also they are only displaying undefined when there is a comment (I checked on my phone, if the pic has no comment, there is no alert. If the picture has comments there is 1 alert for each comment.Can someone please help me out?
The value in pictures[i].comments.data is an array, as shown in the "Response" section in the /tags/tag-name/media/recent Instagram API docs:
[{
"type": "image",
...
"comments": {
"data": [{
"created_time": "1296703540",
"text": "Snow",
...
},
{
"created_time": "1296707889",
"text": "#snow",
...
}],
...
As you can see, the data property in the comments object is an array (beginning with [).
You're misusing for..in on an array. (See Why is using "for...in" with array iteration a bad idea?) for..in loops over property names of objects. The value of comment will always be a property name string, which certainly has no text property.
Instead, you need a plain for loop, because pictures[i].comments.data is an array:
for (var j=0; j<pictures[i].comments.data.length; j++) {
var comment = pictures[i].comments.data[j];
alert(comment.text)
}
One important note is that even if pictures[i].comments.data had been a non-array object, your use of for..in still wouldn't be quite right. Your variable comment holds property names, and you need to use property-access to get the value that a property name refers to:
for (commentKey in pictures[i].comments.data) {
var commentValue = pictures[i].comments.data[commentKey];
alert(commentValue.text)
}
Note that this might work for arrays, but:
The property names may not loop in numerical order
This will loop over all iterable properies of the array, not just numeric indices
So i have two very large multidimesional arrays(4000+). I get the first array as a response from the server where, i have to create dom nodes for each of these array elements. Once this process is finished i have to send another request where i will get another list of elements which will be a subset of the first list, based on the second list i have to modify some elements in the first list (and reflect these changes in the DOM as well). This process takes a very long time to finish, is there any way to accomplish this without two for loops? Or perhaps a faster comparison?
Scenario
The real world example would be as follows, consider a group of people
in a particular area (arr1). In DOM this would be represented as
CheckBox - Name Now consider a group of people who have been administered with a
particular vaccine (arr2), Now arr2 has the list of elements for which
the checkbox should be checked. The whole list(arr1's dom representation) has to be shown at all
costs.
Arrays are of the type
[ ["index", "name", "age"],............. ["4000-index", "4000-name", "4000-age"]]
Here is a pseudo code..
//First request, get the response (resp) and Create DOM elements accordingly
for(var i=0, iLen=resp.length; i<iLen; i++)
{
// Checkbox and <span id='entry-"+resp[i][0]+"'>resp[i][1]</span>
}
// At a later stage in the code...
//Request server for the second list get the response (resp)
arr2 = resp // Second Array
// Walk through the dom, get the list of span elements and store their ids in an array arr1
for(var i=0, iLen=arr1.length; i<iLen; i++)
{
for(var j=0, jLen= arr2.length; j<jLen; j++)
{
if(+arr2[j][0] === +arr1[i][0])
{
//checkbox attr = 'checked'
}
}
}
If you send in the second set of data that your receive as an object with the following structure, you could get some really good performance boost.
var subset = {
anID:{
bcg: true,
someOtherProp: false,
//and so on
}
}
Then all you need to modify your DOM elements -
for (var id in subset){
//do whatever needs to be done
}