Multiple for loops inside each other - javascript

I've bumped into a problem which really bothers me. I've tried to Google the problem but without luck. I got the following code where I want to apply specific nodes to the DOM, getting the information from an array. The while loop works perfectly fine, but it's when it comes to the "for" loop stuff gets funky. I want to filter the different bits using the "collection_id" from the "bitValues" array up against the "collectionValues" id's. The information which should be applied looks like following:
var bitValues = [{
'id': 1,
'collection_id': 1,
'description': "Amazing description",
'radio': "ANR",
'date': "01-01-2018",
'time': "11:45:00",
'seconds': 10,
'delta': '8.5',
'gain_loss': '2',
'total_listeners': '13.343',
'delta_listeners': '22.340',
}, {
'id': 2,
'collection_id': 2,
'description': "DR P3 music is amazing",
'radio': "DR P3",
'date': "05-01-2018",
'time': "13:45:00",
'seconds': 16,
'delta': '12',
'gain_loss': '82',
'total_listeners': '15.343',
'delta_listeners': '102.340',
},
{
'id': 3,
'collection_id': 2,
'description': "Let's go!",
'radio': "Nova FM",
'date': "25-01-2018",
'time': "23:45:00",
'seconds': 126,
'delta': '53',
'gain_loss': '17',
'total_listeners': '28.343',
'delta_listeners': '22.340',
}
];
let collectionValues = [{
'id': 1,
'demographic': "All females",
'delta': "19.5",
'gain_loss': "62.126",
'total_listeners': '43.343',
'delta_listeners': '22.340',
bits: bitValues
}, {
'id': 2,
'demographic': "All 12-24",
'delta': "10.5",
'gain_loss': "52.126",
'total_listeners': '153.343',
'delta_listeners': '132.340',
bits: bitValues
}];
The jQuery to apply the data looks like this:
while (i < collectionAmount) {
(Code that works)...
for (let n = 0; n < bitAmount; n++) {
collection_id = collectionValues[i].id;
bit_reference_id = bitValues[n].collection_id;
if(collection_id == bit_reference_id) {
$('.freestyle-deltas_details_bits').append(`
<tr>
<td><span
class="font-weight-bold">Bit
${bitValues[n].id}: </span>(
${bitValues[n].time}, ${bitValues[n].seconds} sec)</td>
<td><span class="colorChangeByValueDelta">${bitValues[n].delta}%</span></td>
<td><span class="colorChangeByValueGainLoss">${bitValues[n].gain_loss}%</span></td>
<td>${bitValues[n].total_listeners}</td>
<td>${bitValues[n].delta_listeners}</td>
</tr>
`);
}
};
i++;
}
Can anyone help with this problem?
Thanks!

Excellent time to use double filtering! This is why I love Javascript.
const result = first.filter(firstId => second.filter(secondId => firstId.id === secondId.id))
What filter does is that it goes through all the elements in an array and applies logic on it. Since filter takes a function as an argument, it's a perfect way to apply the second filtering on top of it.
If you use bitValues as the first array you'll end up with a list containing the objects in bitValues that are matched in collectionValues.

let table = document.createElement("table");//creating a table element
$(table).append("<tr></tr>");// inserting a row element in the table
let head = table.getElementsByTagName("tr")[0];// selecting the row elment previously selected
for(key in bitValues[0]){//looping through all keys in the object
$(head).append(`<th>${key}</th>`);// putting each key as a head in the first row of the table
}
$(table).append("<tbody></tbody>")// creating a table body
for(row of bitValues){// looping through each object in bitValues array
let tr = document.createElement("tr");// creating a row for each object in the array
for(x in row){// looping through each key of an object in the array
$(tr).append(`<td>${row[x]}<td>`);// putting the value of each key in a td tag and appending it to tr
}
table.appendChild(tr);// appending the tr to the table
}
$('.freestyle-deltas_details_bits').append(table);
Hope it helps

Although I don't know what collectionAmount is, I'd guess it is just the number of items in collectionValues.
What you want is a way, to only render those items from bitValues, which have a collection_id that corresponds to the id of the collection currently being worked on, right? In this case, use a filter and a reduce. Since you can loose the filter by adding a simple if-else inside the reduce, just use a reduce.
First off, let's clean up a bit and move the template into it's own function:
/* template */
const bitValueTemplate = data => `<tr>
<td>
<span class="font-weight-bold">Bit ${data.id}:</span>
(${data.time}, ${data.seconds} sec)
</td>
<td>
<span class="colorChangeByValueDelta">${data.delta}%</span>
</td>
<td>
<span class="colorChangeByValueGainLoss">${data.gain_loss}%</span>
</td>
<td>${data.total_listeners}</td>
<td>${data.delta_listeners}</td>
</tr>`;
Now to the tricky bit, which will build a complete HTML string from the list of bitValues at once (meaning it contains multiple <tr> elements):
/* renders a list into a simple string, only adds items where a certain
property has a certain value. uses a template function/render function to
render each item, then adds it to the end of the overall string. returns
the complete string when finished */
const renderByPropWith = (prop, value, renderFn, xs) => {
return xs.reduce((html, x) => {
if (x[prop] === value) {
return html + renderFn(x);
}
return html;
}, '');
}
OK, time to try it:
var collectionAmount = collectionValues.length;
var i = 0;
while (i < collectionAmount) {
// (Code that works)...
var html = renderByPropWith('collection_id', collectionValues[i].id, bitValueTemplate, bitValues);
$('.freestyle-deltas_details_bits').append(html);
i++;
}

Related

Loop through two arrays of objects Javascript

so I want to load multiple cards into 1 div box, some with same css classes, some different, so I create one array of objects to load these different classes into them. In the mean time, I have other array of object including data to load too.
What I'm trying here is loops these 2 arrays in 1 for loop statement (same length by using same iterator) but one of them returned with undefined, can someone help me please, what am I doing wrong? (The code below is just a simple version of mine because the real one is really long)
<div id="recently-added" class="u-repeater">
// here to load multiple cards
</div>
var DB = [];
$.get(`http://localhost:8080/real-estate/monthly`, function(res) {
DB.push(res);
})
load();
function load() {
var card = '';
var classNum = [
{ 'list': '1', 'text': '2'},
{ 'list': '2', 'text': '5'}
];
for (let index = 0; index < classNum.length; index++) {
var data = DB[index];
card +=
`
<div class="u-list-item-${classNum[index].list}">
<div class="u-container-layout-${classNum[index].list}">
<img class="u-image-${classNum[index].list}">
<p class="u-text">Click to select the text box</p>
<h4 u-text-${classNum[index].text}">${data.title}</h4>
<a class="u-btn-${classNum[index].list}">detail</a>
</div>
</div>
`
}
$("#recently-added").html(card);
}
The DB I took from ajax call and it somehome looks like this:
var DB = [
{'id': 1350, 'title': '2-room apartment with pool view', 'type': 0, 'request': 0},
{'id': 1351, 'title': 'newly built house', 'type': 0, 'request': 1}
];
As I watched them through console, the data variable returned undefined https://imgur.com/a/6ZlWc4C
This is kind of the result I expect: https://imgur.com/a/ttkh17I
$.get(`http://localhost:8080/real-estate/monthly`, function(res) {
load(res.data); // or however your get call returns the atual array you want
})
function load(DB) {
var card = '';
var classNum = [
{ 'list': '1', 'text': '2'},
{ 'list': '2', 'text': '5'}
];
// note that now you are checking two arrays using the length of only one array
// and you dont control the other array,
//so this will likely cause you problems if only 1 element is returned from
// the get call
for (let index = 0; index < classNum.length; index++) {
var data = DB[index];
card +=
`
<div class="u-list-item-${classNum[index].list}">
<div class="u-container-layout-${classNum[index].list}">
<img class="u-image-${classNum[index].list}">
<p class="u-text">Click to select the text box</p>
<h4 u-text-${classNum[index].text}">${data.title}</h4>
<a class="u-btn-${classNum[index].list}">detail</a>
</div>
</div>
`
}
$("#recently-added").html(card);
}
Try this one - call load() only after fetching results:
$.get(`http://localhost:8080/real-estate/monthly`, function(res) {
DB.push(res);
load();
})

How to restrict HTML column elements based on maximum value of field in array of objects

I have a pug template that receives an array of objects from Node/mongo/express. Based on the maximum value of one the fields (not length), I need to restrict some columns in an html table.
For example, the objects rendered from node might look like this
{
quantity: 4,
years: 6
},
{
quantity: 78,
years: 2
}
I would then need to restrict the number of 'year' columns in my table to be 6. I'm not sure what the best way is to do this, whether to render an additional 'max' variable in node, whether I can do this in pug, or if I should use some client-side js. In (very) pseudo-code, I want something like this...
forEach(Math.max(project.output.years)){
...create an html table 'year' column
}
I'm not sure if pug is the right tool for doing this kind of data manipulation.
From the nodejs side, you could use a reducer to find the maximum years value and send it down with the rest of your data.
const data = [{
quantity: 4,
years: 6,
},
{
quantity: 78,
years: 2,
},
]
const maxYears = data.reduce((acc, current) => current.years >= acc ? current.years + acc : acc, 0)
console.log(maxYears) // 6
Or make the reducer a bit more flexible in terms of which field it accesses to make a comparison.
const data = [{
quantity: 4,
years: 6,
},
{
quantity: 78,
years: 2,
},
]
const findMaxVal = (property, data) =>
data.reduce(
(accumulator, current) =>
current[property] > accumulator ? current[property] : accumulator,
0
)
console.log(findMaxVal("years", data)) // 6
There are a number of ways to find the max, but the easiest is:
var maxYears = 0;
for(var i = 0 ; i < data.length ; i++){
if( data[i].years > maxYears ){
maxYears = data[i].years;
}
}
(I'm assuming that the array you have all those objects in is called data)
Then you can pass maxYears into the pug template and do this:
table
tr
- var col = 0;
while col < maxYears
td(colIndex= col++)
This will produce a table that looks like this for maxYears = 3:
<table>
<tr>
<td colIndex='0'></td>
<td colIndex='1'></td>
<td colIndex='2'></td>
</tr>
</table>
Just repeat that loop for every row.

Compare 'order' to 'products' array and print product names if in the order?

I have an array of products:
const products = {
{
id: item1,
name: 'My product',
price: 100
},
{
id: item2,
name: 'My other product',
price: 200
},
{
id: item3,
name: 'My other other product',
price: 150
}
}
I also have an order state which says which and how many products are in the order. In this instance there is 1 item1 and 2 item2 in the order:
const orderState = {
item1: 1,
item2: 2
}
How can I render the prodcut name and number of times its in the order? Im using React but I think vanilla JS is all thats needed for the solution.
My HTML output needs to be something like:
<ul>
<li>My product x1 = $1</li>
<li>My other product x2 = $4</li>
</ul>
The way you're storing your data is what seems to be giving you difficulty. First, make the array of products an actual array:
const products = [
{
id: item1,
name: 'My product',
price: 100
}, //...
];
Additionally, what is that item1 value? Is that an actual ID of some kind, like an integer? That's what you'll use to identify the products later.
Then for your "order state", create an object which holds an array of references to the products. Something like this:
const orderState = {
products: [
{
productID: item1,
quantity: 1
},
{
productID: item2,
quantity: 2
}
]
}
Now that you have a more sensible data structure, you can more easily operate on that structure. There are a variety of ways to build your output, and how you do it may depend heavily on what other tools you're using (such as template engines and such). But just to demonstrate the logic, let's build a simple string:
var output = '<ul>';
for (var i = 0; i < orderState.products.length; i++) {
var product = {};
for (var j = 0; j < products.length; j++) {
if (products[j].id == orderState.products[i].id) {
product = products[j];
break;
}
}
output += '<li>' + product.name + ' x' + orderState.products[i].quantity + ' = $' + (product.price * orderState.products[i].quantity) + '</li>';
}
output += '</ul>';
From this you can refactor and improve as needed. For example, the logic to find the product based on its id can be extracted into its own function, or perhaps even reduced to a one-liner using built-in JavaScript functionality or framework functionality. (underscore.js is good for functionality like that.)
But the basic idea is to keep the data structures sensibly organized. Then the code which operates on those structures becomes trivial.
With below solution you can get the names of selected products as per your question statement.
let productsOrderedIds = Object.keys(orderState);
let proNames = [];
for(let pro of products) {
if(productsOrderedIds.indexOf(pro.id) !== -1){
proNames.push(pro.name);
}
}
console.log(proNames);

Complex array ordering

Suppose I have the following array:
var articles = [
{
id: 7989546,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
//...
]
Each element of this array represents (partially) some kind of content in our website. It has an id and is tagged with people (#6) and/or topics (#7).
The user is going to be provided a cookie containing the suggested or recommended tags, like this:
var suggestions = [
"46#6",
"107#6",
"48793#7"
]
Consider these tags like suggestions that will be shown to the end user, like "Maybe you are interesed in reading..."
The suggestions array is already ordered by tag prioritiy. This means, that the first tag is more relevant to the user than the second tag.
Now, what I want to do is to order my articles array in the same way, that is, by tag priority.
No filters should be applied as the articles array is guaranteed to have elements that have at least one tag from the suggestions array.
If I have an article with tags: [ "98#6", "107#6", 558234#7" ] and another one with tags: [ "46#6", "36987#7" ], I want the latter to be first, because the tag 46#6 has more priority than 107#6 in the suggestions array.
How can I achieve this kind of ordering (using two arrays)?
Note: jQuery solutions are gladly accepted.
jsFiddle Demo
Just make your own sort function and then use .indexOf in order to check for tag existence. The issue that you are going to have to decide to handle on your own is what makes the most sense for collisions. If an article is tagged with a priority 1 tag, but another article is tagged with 3 lower priority tags, who gets precedence? There is some logic involved there and in my suggested solution I simply just take a total of the priority by using the length of suggestions and summing the priorities. This can be adapted to give a different type of collision detection if you wish, but the approach will be basically the same.
Step 1: Create the compare function
This is going to order the array descending base on the result from tagCount. Which is to say that if tagCount returns a value of 6 for right, and a value of 3 for left, then 6 is ordered first.
var compareFn = function(left,right){
return tagCount(right.tags) - tagCount(left.tags);
};
Step 2: Create the tagCount "algorithm" for determining priority
This simply gives precedence to the earliest occurring match, but will also give some weight to multiple later occurring matches. It does this by taking the matched index subtracted from the length of the match array (suggestions). So if there are 5 suggestions, and the first suggestion is matched, then that is going to end up being a value of 5 (length=5 - index=0).
var tagCount = function(tags){
var count = 0;
for(var i = 0; i < tags.length; i++){
var weight = suggestions.indexOf(tags[i]);
if(weight > -1)
count += tags.length - weight;
}
return count;
}
Stack Snippet
var articles = [
{
id: 7989546,
tags: [
"107#6",
"558234#7"
]
},
{
id: 756,
tags: [
"98#6",
"558234#7"
]
},
{
id: 79876,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
{
id: 7984576,
tags: [
"98#6",
"107#6",
"46#6"
]
}
];
var suggestions = [
"46#6",
"107#6",
"48793#7"
];
var compareFn = function(left,right){
return tagCount(right.tags) - tagCount(left.tags);
};
var tagCount = function(tags){
var count = 0;
for(var i = 0; i < tags.length; i++){
var weight = suggestions.indexOf(tags[i]);
if(weight > -1)
count += tags.length - weight;
}
return count;
}
var a = articles.sort(compareFn);
console.log(a);
document.querySelector("#d").innerHTML = JSON.stringify(a);
<div id="d"></div>
My approach: Sort by sum of relevance score
Give you have:
var articles = [
{
id: 7989546,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
{
id: 8000000,
tags: [
"107#6",
"107#10",
"558234#7",
"5555#1"
]
},
{
id: 8333000,
tags: [
"46#6",
"107#6",
"666234#7",
"107#6"
]
}
];
var suggestions = [
"46#6",
"107#6",
"48793#7"
];
And you want to sort articles by tags whereas tag ranks are defined in suggestions. One simple approach would be:
Step 1) For each article, get index of each tag exists in the suggestion. If it doesn't exist, discard.
Given suggestions ["a","b","c"]
Article tags ["a","b","zzzz","yyyy"]
Will be mapped to index [0,1] (last two tags are discarded because they do not exist in suggestion list)
Step 2) Calculate degree of relevance. Higher-ranked tag (smaller index) yields greater value (see function degreeOfRelevance() below).
Step 3) Sum the total degree of relevance and sort by this value. Thus, the article which contains higher ranked tags (based on suggestions) will yield higher total score.
Quick example:
article <100> with tags: [a,b,c]
article <200> with tags: [b,c]
article <300> with tags: [c,d,e,f]
Given suggestions: [a,b,c]
The articles will be mapped to scores:
article <100> index : [0,1] ===> sum score: 3+2 = 5
article <200> index : [1] ===> sum score: 2
article <300> index : [2] ===> sum score: 1
Therefore, the article <100> is ranked the most relevant document when sorted by score
And below is the working code for this approach:
function suggest(articles, suggestions){
function degreeOfRelavance(t){
return suggestions.length - suggestions.indexOf(t);
}
function weight(tags){
return (tags.map(degreeOfRelavance)).reduce(function(a,b){
return a+b
},0);
}
function relatedTags(a){
return a.tags.filter(function(t){
return suggestions.indexOf(t)>=0
});
}
articles.sort(function(a,b){
return weight(relatedTags(a)) < weight(relatedTags(b))
});
return articles;
}
// See the output
console.log(suggest(articles,suggestions));

Accessing an element in an array within an array in JavaScript

Pastebin of index.html: http://pastebin.com/g8WpX6Wn (this works but with some broken img links & no css).
Zip file if you want to see whole project:
I'm trying to dynamically change the contents of a div when I click an image. The image has it's respective id (the first index in the inner array) within the first inner array there's another array (index 3). I want to populate my div (id="articleLinks") with those links using JQuery when the image is clicked.
JavaScript & JQuery:
The tube array. *Note: the first index of each element in tubeArray is the ID & the news articles aren't linked to anything particular. Only interested in tubeArray[0] & tubeArray[4]
var tubeArray = [
['UQ', -27.495134, 153.013502, "http://www.youtube.com/embed/uZ2SWWDt8Wg",
[
["example.com", "Brisbane students protest university fee hikes"],
["example.com", "Angry protests over UQ student union election"],
]
],
['New York', 40.715520, -74.002036, "http://www.youtube.com/embed/JG0wmXyi-Mw",
[
["example.com" , "NY taxpayers’ risky Wall Street bet: Why the comptroller race matters"]
]
],
['To The Skies', 47.09399, 15.40548, "http://www.youtube.com/embed/tfEjTgUmeWw",
[
["example.com","Battle for Kobane intensifies as Islamic State uses car bombs, Syrian fighters execute captives"],
["example.com","Jihadists take heavy losses in battle for Syria's Kobane"]
]
],
['Fallujah', 33.101509, 44.047308, "http://www.youtube.com/embed/V2EOMzZsTrE",
[
["example.com","Video captures family cat saving California boy from dog attack"],
["example.com","Fines of £20,000 for dogs that chase the postman"]
]
]
];
A for loop which goes through each element in tubeArray then assigns id to the first index. Also an image that calls the function myFunctionId which takes the parameter this.id.
for (i = 0; i < tubeArray.length; i++) {
var id = tubeArray[i][0];
//other code
'<img src="img.png" onclick="myFunctionId(this.id);" id="' + id + '">' +
//other code
}
function myFunctionId (id) {
journal = id;
alert(journal) //just a test
//I want to search through tubeArray with the id and find the matching inner array.
//I then want to loop through the innerArray and append to my html a link using JQuery.
for (j = 0; i < innerArray.length; j++){
//supposed to get "www.linkX.com"
var $link = ;
//supposed to get "titleX"
var $title = ;
//change the content of <div id="articleLinks">
$('#articleLinks').append('<a href=$link>$title</a><br>');
}
}
HTML:
<div id="articleLinks">
Example Link<br>
</div>
Any help would be greatly appreciated. I've tried to simplify & cut out as much as I can so it's readable.
probably this might help: make yourself a map like
var tubeArray = [
[ // tubeArray[0]
'id', // tubeArray[0][0]
int, // tubeArray[0][1]
int, // tubeArray[0][2]
[ // tubeArray[0][3]
[ // tubeArray[0][3][0]
"www.link1.com", // tubeArray[0][3][0][0]
"title1" // tubeArray[0][3][0][1]
],
[ // tubeArray[0][3][1]
"www.link2.com", // tubeArray[0][3][1][0]
"title2" // tubeArray[0][3][1][1]
]
]
],
etc.
don't know whether this helps, but four dimensional arrays are brain-breaking ....
[edit]
... and thus go for a more OO like approach:
var tubeArray = [
'id' : { // tubeArray[id] or tubeArray.id
'a': int, // tubeArray.id.a
'b': int, // tubeArray.id.b
'entries': [ // tubeArray.id.entries
{ // tubeArray.id.entries[0]
'url': "www.link1.com", // tubeArray.id.entries[0].url
'title': "title1"
},
{ // tubeArray.id.entries[1]
'url': "www.link2.com", // tubeArray.id.entries[1].url
'title': "title2" ...
}
]
] ,
First you need to loop over tubeArray then go 4 deep and loop over the Array of Arrays at that level. Of course, you loop over those inner Arrays and get Elements 0 and 1.
$.each(tubeArray, function(z, o){
$.each(o[4], function(i, a){
$.each(a, function(n, v){
$('#articleLinks').append("<a href='"+v[0]+"'>"+v[1]+'</a>'); // use CSS to break lines
});
});
}

Categories

Resources