Loop through two arrays of objects Javascript - 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();
})

Related

Multiple for loops inside each other

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++;
}

How to remove duplicate values from dynamic select in Javascript?

trying to figure this out with no such luck. Basically we populating a select with values from the service that it's being retrieved from. But there are duplicates in the select. Here's the code that's doing it. I'd like to remove the duplicates from what's getting returned that are in the "theProduct.name". I know this question has been asked before but I can't figure this out. The image attached is the select where it's happening. Thanks
function populateSearchProducts(data) {
var theData = data.data.results;
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
Try a filter to remove duplicates from the input data:
function populateSearchProducts(data) {
var theData = data.data.results.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
});
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
function populateSearchProducts(data) {
data = data.data.results;
const dupes = new Set();
for(const {name, id} of Object.values(data)){
if(dupes.has(name)) continue;
$field.append(`<option value='${id}' > ${name} </option>`);
dupes.add(name);
}
}
You can keep track of the items already added to the DOM and then use a filter before adding new elements.
In the code below, the filter is looking at the id of each element to filter them out. If you want, you could use name (or any other attribute) to detect the duplicates and filter them out.
Something along the lines of:
var dataArray = [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
{id: 4, name: 'four'},
{id: 2, name: 'two'}, // dupe
{id: 5, name: 'five'},
{id: 3, name: 'three'} // dupe
]
function populateSearchProducts(data, $field) {
//var theData = data.data.results;
var theData = data;
$field.empty().append('<option value=""> </option>');
// to keep track of the ones already in the drop-down
var alreadyAdded = [];
for (let p of theData) {
if(alreadyAdded.filter( item => item.id === p.id ).length <= 0 ){
$field.append('<option value="'+p.id+'">'+p.name+'</option>');
alreadyAdded.push(p);
}
}
}
// when docuemnt ready, populate the field
$(function(){
var field = $("#selOptions");
populateSearchProducts(dataArray, field);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select id="selOptions" />
updated based on comments

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);

Render each Objects in an array in HTML from JavaScript

I have a JavaScript array of objects taken from a JSON file. Each object in the array represents a product. The following code shows the JavaScript. The console.log displays each element in the console, however the innerHTML only renders the last as this is the last value to be rendered.
/* global $ */
var output = document.getElementById('output');
var products = [];
$.getJSON('/products', function(data){
output.innerHTML = data
for(var keys in data){
console.log(data[keys]);
products.push(data[keys]);
output.innerHTML = data[keys].NAME;
}
// output.innerHTML = products;
//console.log(products)
});
I want each product to be rendered in it's own output div. How would I display each element in the HTML instead of just the last?
Just append the element to your output. Actually you did it.
Change this:
output.innerHTML = data[keys].NAME;
To this:
$('#output').append(`<div>${data[keys].NAME}</div>`);
So:
for(var keys in data){
console.log(data[keys]);
products.push(data[keys]);
$('#output').append(`<div>${data[keys].NAME}</div>`);
}
I would recommend you also to change the for...in with a simple forEach, so to change the loop into this:
data.forEach((el) => {
products.push(el);
$('#output').append(`<div>${el.NAME}</div>`);
});
The error in your code was just that you were overriding the HTML content of your element every time. If you change:
output.innerHTML = data[keys].NAME;
to this:
output.innerHTML += data[keys].NAME;
It should already work
You can use jQuery "append". You dont need to make product object.
Try like this:
for(var keys in data){
$(".productList").append( "<div>"+data[keys].NAME+"</div>" );
}
Basically, your output element should be a wrapper containing other elements repeated for each product. One semantic option is to use a list, such as ul + li.
You should iterate your products and append its piece of HTML to an array. When you are done processing them, you assign that chunk of HTML to the output element.
// Just to keep your code untouched:
const $ = {
getJSON(url, callback) {
callback([{
ID: 1,
NAME: 'Product 1',
PRICE: '2.50',
}, {
ID: 2,
NAME: 'Product 2',
PRICE: '1.25',
}, {
ID: 3,
NAME: 'Product 3',
PRICE: '10.00',
}]);
},
};
const output = document.getElementById('output');
let productsArray = [];
$.getJSON('/products', function(products){
productsArray = products;
output.innerHTML = products.map(product => {
return `<li data-id="${ product.ID }">
<span>${ product.NAME }</span>
<span>${ product.PRICE }</span>
</li>`;
}).join('');
});
<ul id="output"></ul>
$('#output').append(`<div>${data[keys].NAME}</div>`);
In reference to #VK321, you can also use the .map method.
data.map(element => {
$(".productList").append( `<div>${element.NAME}</div>` );
})

Handsontable editing nested array

I am using handsontable and am having trouble getting the "beforeChange" and "afterChange" events to fire consistently, which I'm hoping use to commit updates to the database. I am using the following code (version 0.16.1):
HTML:
<div id="table"></div>
<div id="output"></div>
JavaScript:
var data = [{
id: 5,
name: 'Sedan',
price: 2000,
tags: ['pink', 'purple']
}, {
id: 6,
name: 'Truck',
price: 1500,
tags: ['green', 'blue']
}, {
id: 6,
name: 'SUV',
price: 1500,
tags: null
}];
var writeMessage = function(msg) {
var output = document.getElementById("output");
var div = document.createElement('DIV');
div.innerHTML = msg;
output.insertBefore(div, output.firstChild);
console.log(msg);
};
var tableDiv = document.getElementById("table");
this.table = new Handsontable(tableDiv, {
data: data,
colHeaders: ["id", "name", "price", "tags"],
columns: [{
data: "id"
}, {
data: "name"
}, {
data: "price"
}, {
data: "tags"
}],
beforeChange: function(changes, source) {
writeMessage("beforeChange: " + changes + ": " + source);
},
afterChange: function(changes, source) {
writeMessage("After Change fired: " + changes);
if (!changes) {
return;
}
var i, idx, key, newVal, modelID;
for (i = 0; i < changes.length; i++) {
idx = changes[i][0];
key = changes[i][1];
newVal = changes[i][3];
modelID = this.getDataAtRow(idx)[0];
writeMessage("New value: " + key + ": " + newVal);
}
}
});
http://codepen.io/anon/pen/GjzrdX?editors=0010
The event handlers fire when I'm editing the text and number fields and for the when tags are null, but do not fire for data objects with tag arrays (e.g. pink,purple; green,blue). How do I get the events to fire for the tag cells without modifying the data structure? Any advice would be greatly appreciated!
I believe that you are facing a bug here when trying to put an Array in a Cell but I cannot find anywhere in the handsontable documentation or any thread in their GitHub mentioning this issue... IMO, putting an Array in a Cell is suppose to be use as Source and not Data, which result in a cell that you can't edit (hence the events afterChange/beforeChange not triggered). In your example the third line is working because of the 'null' value which is not an Array.
Anyway, the only workaround I managed to do for you is to modify your data after you define your data structure (in order to respect your condition, but I strongly advice do modify them anyway because you will need to do that eventually).
Assuming that your tags can only contain two values :
var data1 = [];
for (var i=0; i<data.length;i++) {
if (data[i].tags != null) {
var temp = data[i].tags[0];
temp = temp.concat(',');
temp = temp.concat(data[i].tags[1]);
} else var temp = null;
data1.push({ id: data[i].id, name: data[i].name, price: data[i].price, tags: temp });
}
If the length of your Arrays tags can be more than that, just do a second loop to cover it.
See your code here with this solution implemented
You then can use data1 to load your table. If you need to save your data after modification, you can use a similar function to reverse it into your original data structure.

Categories

Resources