group by in jquery with .map() - javascript

I have an array where each element has a name and a subsection.
I now want to group those elements by subsection.
Is there a way to do a group by inside a mapping function.
The data looks like:
* 0: "name: Study subSection: Education"
1: "name: Classes subSection: Education"
2: "name: Society subSection: Social”
I want it to appear as
Education
1.Study
2.Classes
Social
1.Society
Here is my code thus far that isn't working. I think it needs a little tweaking to work properly.
let myArray = response.map(item => {
return 'name: ' + item.name + ' subSection: ' + item.subSection;
}
);
let grouppedArray1=_.groupBy(myArray, 'subSection'))

In your case, the Array#map method generates a string array and you are trying to group by subSection property but there is no such property for the string.
You can do something simple using Array#reduce method.
// iterate over the element
let res = response.reduce((obj, item) => {
// define group if not defined(property with subsection name and value as array)
obj[item.subSection] = obj[item.subSection] || [];
// push the value to group
obj[item.subSection].push('name: ' + item.name + ' subSection: ' + item.subSection);
// return the object
return obj;
// set initial value as empty object for result
}, {});
let response = [{
"name": "Study",
subSection: "Education"
}, {
"name": "Classes",
subSection: "Education"
},
{
name: "Society",
subSection: "Social"
}
];
let res = response.reduce((obj, item) => {
obj[item.subSection] = obj[item.subSection] || [];
obj[item.subSection].push('name: ' + item.name + ' subSection: ' + item.subSection);
return obj;
}, {});
console.log(res);
UPDATE : To show them as buttons( combined ), do something like this:
let response = [{
"name": "Study",
subSection: "Education"
}, {
"name": "Classes",
subSection: "Education"
},
{
name: "Society",
subSection: "Social"
}
];
let res = response.reduce((obj, item) => {
obj[item.subSection] = obj[item.subSection] || [];
obj[item.subSection].push(item.name);
return obj;
}, {});
// get values array and iterate
Object.keys(res).forEach(function(k) {
// generate h3 ith subSection value and append
$('#container').append(
$('<h3>', {
text: k,
class : 'title'
})
)
// generate buttons and append
.append(res[k].map(v =>
$('<button>', {
text: v,
class : 'btn btn-default'
})
))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container"></div>

Related

how to get only one value from array data and convert it into comma separted values. using javascript without template literals

i have array data in the format given below
const data= [
{
name: "productname",
id: "1356",
price: "0.00",
category: "Health",
position: "1",
list: "New Products",
stocklevel: "20",
brand: "Health"
},
{
name: "productname2",
id: "5263",
price: "0",
category: "Hair",
position: "2",
list: "New Products",
stocklevel: "",
brand: "Hair"
}]
from this data i want only product name of each product by difference of product1 , product2.
for example i want the data in format of string by comma separated values like given below:-
product1name: "productname",
product2name: "productname2",
...
i tried using map function but not able to take only one or two values from whole array data.
here is the code what i tried
var dataByComa = '';
var Values = data
.map(function (p, i) {
return Object.keys(data[i]).map(function (k) {
return "prod" +" " + ": " + JSON.stringify(data[i][k]);
});
}).map(function (v) {
return v.join(",\n"); });
var commaValues = Values.join(",\n");
return commaValues;
with this code i can able to convert array data into comma separated values but i want only productnames.
Note :- Without using template template literals.
Edit: I'm more clear on what the OP was looking for now - here is an answer that fulfills all the requirements
let commaValues = ""
for (let i = 0; i < data.length; i++) {
commaValues += ("product" + (i + 1) + "name: " + "\"" + data[i]["name"] + "\", ")
}
// result: 'product1name: "productname", product2name: "productname2", '
You can do that using reduce. It takes a function as first and an initial value as second parameter (here an empty object {}).
The variable "previous" keeps track of the current state of the (initially empty) new object while the function adds the name of every single Element from the "data" array to the object.
var result = data.reduce((previous, element) => {
previous[element.name] = element.name;
return previous;
}, {})
EDIT:
As i realized, you actually want a string as result. In that case you could do:
var result = data.reduce((previous, element) => {
previous[element.name] = element.name;
return previous;
}, {})
var csvWithBrackets = JSON.stringify(result)
var csv = csvWithBrackets.substring(1, csvWithBrackets.length-1)
However the answer from Pzutils seems more compact.
You can iterate through the data and assign the value to an external variable like this:
let finalData = {}
data.forEach((item, index) => {
finalData[`product${index+1}name`] = item.name
})

How to get the index of object/array by comparing its checked value in javascript

I have an array of object 'obj', whose values are populating into html as checkbox value. Now when I uncheck any checkbox, I need to get the index of array of object of which its belong to by comparing with the array of object 'obj'.For Ex- suppose I uncheck first 'createddate' , then I should get index as 0 so on.. Here is the code below
html
<div class="menu-outer-block">
<div class="menu-block" id="menu-block">
<div id="menu"></div>
</div>
</div>
script
let Obj = /* this.guidelineObj;*/ [{
mainitem: "My item 1",
Seconditem: {
createddate: "30-01-02",
enddate: "30-01-03"
}
},
{
mainitem: "My item 2",
Seconditem: {
createddate: "30-01-02",
enddate: "30-01-03"
}
}
];
const lines1 = [];
const columns = {};
Obj.reverse().forEach(col => {
lines1.push('<div>' + col.mainitem + '</div>');
Object.keys(col.Seconditem).forEach(key => {
columns[key] = true;
const checked = columns[key] ? 'checked' : '';
lines1.push(
"<label><input class='menu-checkbox' type='checkbox' name='" +
key +
`' ` +
checked +
'>' +
key +
'</label>'
);
});
});
document.getElementById("menu").innerHTML = lines1.join('<br>');
const dropDown = document.querySelector('#menu');
dropDown.setAttribute('style', 'display:block;');
dropDown.addEventListener('change', e => {
console.log(e)
})
Since you are already creating the checkboxes dynamically you can simply add a data attribute holding the mainitem value of the object. It is then trivial to retrieve this from the event.target (event.target.dataset.mainitem) and use it to either find() the object in the array or findIndex() of the object in the array by comparing mainitem values.
const mainitem = e.target.dataset.mainitem;
// find the object
const o = Obj.find(o => o.mainitem === mainitem);
// or find the index
const oIndex = Obj.findIndex(o => o.mainitem === mainitem);
Note you are calling reverse() on your array before your loop, which mutates your array in place, so the results of findIndex() will be reversed from the original order.
let Obj = [{ mainitem: "My item 2", id: 1, Seconditem: { createddate: "30-01-02", enddate: "30-01-03" } }, { mainitem: "My item 2", id: 2, Seconditem: { createddate: "30-01-02", enddate: "30-01-03" } }, { mainitem: "My item 2", id: 3, Seconditem: { createddate: "30-01-02", enddate: "30-01-03" } }];
const lines1 = [];
const columns = {};
Obj.reverse().forEach(col => {
lines1.push('<div>' + col.mainitem+ ' id: '+ col.id + '</div>');
Object.keys(col.Seconditem).forEach(key => {
columns[key] = true;
const checked = columns[key] ? 'checked' : '';
lines1.push(
`<label>
<input class='menu-checkbox' type='checkbox' name='${key}' data-id='${col.id}' ${checked} >
${key}
</label>`
);
});
});
document.getElementById("menu").innerHTML = lines1.join('<br>');
const dropDown = document.querySelector('#menu');
dropDown.setAttribute('style', 'display:block;');
dropDown.addEventListener('change', e => {
const id = e.target.dataset.id;
// find the object
const o = Obj.find(o => o.id == id);
// or find the index
const oIndex = Obj.findIndex(o => o.id == id);
console.log(oIndex, ' - ', o);
})
<div class="menu-outer-block">
<div class="menu-block" id="menu-block">
<div id="menu"></div>
</div>
</div>

Merge Array of same level

I have an array which I need to combine with comma-separated of the same level and form a new array.
Input:
let arr = [
[{ LEVEL: 1, NAME: 'Mark' }, { LEVEL: 1, NAME: 'Adams' }, { LEVEL: 2, NAME: 'Robin' }],
[{ LEVEL: 3, NAME: 'Williams' }],
[{ LEVEL: 4, NAME: 'Matthew' }, { LEVEL: 4, NAME: 'Robert' }],
];
Output
[
[{ LEVEL: 1, NAME: 'Mark,Adams' }, { LEVEL: 2, NAME: 'Robin' }],
[{ LEVEL: 3, NAME: 'Williams' }],
[{ LEVEL: 4, NAME: 'Matthew,Robert' }],
];
I tried with the following code but not getting the correct result
let finalArr = [];
arr.forEach(o => {
let temp = finalArr.find(x => {
if (x && x.LEVEL === o.LEVEL) {
x.NAME += ', ' + o.NAME;
return true;
}
if (!temp) finalArr.push(o);
});
});
console.log(finalArr);
You could map the outer array and reduce the inner array by finding the same level and add NAME, if found. Otherwise create a new object.
var data = [[{ LEVEL: 1, NAME: "Mark" }, { LEVEL: 1, NAME: "Adams" }, { LEVEL: 2, NAME: "Robin"}], [{ LEVEL: 3, NAME: "Williams" }], [{ LEVEL: 4, NAME: "Matthew" }, { LEVEL: 4, NAME: "Robert" }]],
result = data.map(a => a.reduce((r, { LEVEL, NAME }) => {
var temp = r.find(q => q.LEVEL === LEVEL);
if (temp) temp.NAME += ',' + NAME;
else r.push({ LEVEL, NAME });
return r;
}, []));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Assuming you only want to merge within the same array and not across arrays, and assuming there aren't all that many entries (e.g., fewer than several hundred thousand), the simple thing is to build a new array checking to see if it already has the same level in it:
let result = arr.map(entry => {
let newEntry = [];
for (const {LEVEL, NAME} of entry) {
const existing = newEntry.find(e => e.LEVEL === LEVEL);
if (existing) {
existing.NAME += "," + NAME;
} else {
newEntry.push({LEVEL, NAME});
}
}
return newEntry;
});
let arr= [
[{"LEVEL":1,"NAME":"Mark"},
{"LEVEL":1,"NAME":"Adams"},
{"LEVEL":2,"NAME":"Robin"} ],
[{"LEVEL":3,"NAME":"Williams"}],
[{"LEVEL":4,"NAME":"Matthew"},
{"LEVEL":4,"NAME":"Robert"}]
];
let result = arr.map(entry => {
let newEntry = [];
for (const {LEVEL, NAME} of entry) {
const existing = newEntry.find(e => e.LEVEL === LEVEL);
if (existing) {
existing.NAME += "," + NAME;
} else {
newEntry.push({LEVEL, NAME});
}
}
return newEntry;
});
console.log(result);
If the nested arrays can be truly massively long, you'd want to build a map rather than doing the linear search (.find) each time.
I'd try to do as much of this in constant time as possible.
var m = new Map();
array.forEach( refine.bind(m) );
function refine({ LABEL, NAME }) {
var o = this.get(NAME)
, has = !!o
, name = NAME
;
if (has) name = `${NAME}, ${o.NAME}`;
this.delete(NAME);
this.set(name, { NAME: name, LABEL });
}
var result = Array.from( m.values() );
I haven't tested this as I wrote it on my phone at the airport, but this should at least convey the approach I would advise.
EDIT
Well... looks like the question was edited... So... I'd recommend adding a check at the top of the function to see if it's an array and, if so, call refine with an early return. Something like:
var m = new Map();
array.forEach( refine.bind(m) );
function refine(item) {
var { LABEL, NAME } = item;
if (!NAME) return item.forEach( refine.bind(this) ); // assume array
var o = this.get(NAME)
, has = !!o
, name = NAME
;
if (has) name = `${NAME}, ${o.NAME}`;
this.delete(NAME);
this.set(name, { NAME: name, LABEL });
}
var result = Array.from( m.values() );
That way, it should work with both your original question and your edit.
EDIT
Looks like the question changed again... I give up.
Map the array values: every element to an intermediate object, then create the desired object from the resulting entries:
const basicArr = [
[{"LEVEL":1,"NAME":"Mark"},
{"LEVEL":1,"NAME":"Adams"},
{"LEVEL":2,"NAME":"Robin"} ],
[{"LEVEL":3,"NAME":"Williams"}],
[{"LEVEL":4,"NAME":"Matthew"},
{"LEVEL":4,"NAME":"Robert"}]
];
const leveled = basicArr.map( val => {
let obj = {};
val.forEach(v => {
obj[v.LEVEL] = obj[v.LEVEL] || {NAME: []};
obj[v.LEVEL].NAME = obj[v.LEVEL].NAME.concat(v.NAME);
});
return Object.entries(obj)
.map( ([key, val]) => ({LEVEL: +key, NAME: val.NAME.join(", ")}));
}
);
console.log(leveled);
.as-console-wrapper { top: 0; max-height: 100% !important; }
if you want to flatten all levels
const basicArr = [
[{"LEVEL":1,"NAME":"Mark"},
{"LEVEL":1,"NAME":"Adams"},
{"LEVEL":2,"NAME":"Robin"} ],
[{"LEVEL":3,"NAME":"Williams"}],
[{"LEVEL":4,"NAME":"Matthew"},
{"LEVEL":4,"NAME":"Robert"},
{"LEVEL":2,"NAME":"Cynthia"}],
[{"LEVEL":3,"NAME":"Jean"},
{"LEVEL":4,"NAME":"Martha"},
{"LEVEL":2,"NAME":"Jeff"}],
];
const leveled = basicArr.map( val => Object.entries (
val.reduce( (acc, val) => {
acc[val.LEVEL] = acc[val.LEVEL] || {NAME: []};
acc[val.LEVEL].NAME = acc[val.LEVEL].NAME.concat(val.NAME);
return acc;
}, {}))
.map( ([key, val]) => ({LEVEL: +key, NAME: val.NAME.join(", ")})) )
.flat() // (use .reduce((acc, val) => acc.concat(val), []) for IE/Edge)
.reduce( (acc, val) => {
const exists = acc.filter(x => x.LEVEL === val.LEVEL);
if (exists.length) {
exists[0].NAME = `${val.NAME}, ${exists.map(v => v.NAME).join(", ")}`;
return acc;
}
return [... acc, val];
}, [] );
console.log(leveled);
.as-console-wrapper { top: 0; max-height: 100% !important; }
ES6 way:
let say attributes is multidimensional array having multimple entries which need to combine like following:
let combinedArray = [];
attributes.map( attributes => {
combined = combinedArray.concat(...attributes);
});

With an identifier traverse through a JSON object and get a specific value with the identifier

Traverse through a JSON object which has nested arrays objects inside it .
The label value is provided which is the identifier with which need to return the associated level metrics value . If the label is found in the 2nd level find the metrics at the second level and it should be returned
I couldn't get the logic on how to traverse through an object and return the specific value
function getMetrics(arr, label) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
return getMetrics(arr[i].children, label);
}
}
return "Not found";
}
const selectedMetrics = getMetrics(dataObj.series, '1');
Consider the JSON object with children specifies the sub level of the current level .
const dataObj = {
series: [
{
label: "A",
metricsValue: "ma",
children: [
{
label: "A-B",
value: 6,
metricsValue: "ma-mb"
},
{
label: "A-B-C",
metricsValue: "ma-mb-mc",
children: [
{
label : "A-B-C-D",
value: 6,
metricsValue: "ma-mb-mc-md"
}
]
}
]
},
{
label: "1",
metricsValue: "m1",
}
]
};
Expected Result :
When the input is "1", it should return
selectedMetrics= "m1"
Input : "A-B-C-D"
selectedMetrics= "ma-mb-mc-md"
You can perform a Depth first search (DFS) or Breadth first search (BFS) to find metricValues at any level.
Here I'm using DFS to find the required value. This works for data with any nested levels.
const dataObj = { series: [ { label: "A", metricsValue: "ma", children: [ { label: "A-B", value: 6, metricsValue: "ma-mb" }, { label: "A-B-C", metricsValue: "ma-mb-mc", children: [ { label: "A-B-C-D", value: 6, metricsValue: "ma-mb-mc-md" } ] } ] }, { label: "1", metricsValue: "m1"} ] };
function getMetrics(arr, label) {
var result;
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
result = getMetrics(arr[i].children, label);
if (result) {
return result;
}
}
}
return null;
}
console.log("selectedMetrics for 'A' = " + getMetrics(dataObj.series, 'A'));
console.log("selectedMetrics for 'A-B' = " + getMetrics(dataObj.series, 'A-B'));
console.log("selectedMetrics for 'A-B-C' = " + getMetrics(dataObj.series, 'A-B-C'));
console.log("selectedMetrics for 'A-B-C-D' = " + getMetrics(dataObj.series, 'A-B-C-D'));
console.log("selectedMetrics for '1' = " + getMetrics(dataObj.series, '1'));
Your'e passing in the value, so use it instead of the string & you're not accessing the children nodes.
for(var i=0; i< arr.length;i++){
const x = arr[i];
if (x.children.label === value) {
console.log(x.metricValue)
}else{
x.forEach(element => {
if (element.children.label === value) {
console.log(element.metricValue)
}else{
element.forEach(secondEl =>{
if (secondEl.children.label === value) {
console.log(secondEl.metricValue)
}
})
}
});
}
}
You can create a more elegant way of iterating around the children nodes but that may help you out

Generating Entries with Same Name in JavaScript Object

I'm new to Javascript and I've done a little research but I can't seem to figure out how to generate multiple lists with same name keys but different values. I'm trying to generate code for an embed message which should look something like this:
{embed: {
color: 3447003,
title: "title",
description: "desc",
fields: [{
name: "header 1",
value: "text 1"
},
{
name: "header 2",
value: "text 2"
},
{
name: "header 3",
value: "text 3"
}
]
}
}
this is for generating a list of my commands in an embed automatically so I don't have to keep going back and edit it.
I'm mainly trying to get multiple of the "fields" with the "name" and "value" entries and also trying to add all the commands in a line for the "value".
Here's my code:
let currentCategory = "";
var embed = {
"title": "= __Command List__ =",
"description": `[Use ${message.settings.prefix}help <commandname> for details]`,
"color": 2563607,
fields : []
};
const sorted = myCommands.array().sort((p, c) => p.help.category > c.help.category ? 1 : p.help.name > c.help.name && p.help.category === c.help.category ? 1 : -1 );
sorted.forEach( c => {
const cat = c.help.category.toProperCase();
if (currentCategory !== cat) {
embed.fields = [{name : `${cat}`,value : ""}];
currentCategory = cat;
}
embed.fields[0].value += ` \`${c.help.name}\``;
});
console.log({embed});
message.channel.send({embed});
I used console.log({embed}); to print the code it generates in the console and this is what shows.
{ embed:
{ title: '= __Command List__ =',
description: '[Use y!help <commandname> for details]',
color: 2563607,
fields: [ [Object] ] } }
Ok I figured it out thanks to PM 77-1.
For anyone else who wants to know I basically set and index of -1 and made it add to the index while it looped for every new category.
let currentCategory = "";
let index = -1;
var embed = {
"title": "= __Command List__ =",
"description": `[Use ${message.settings.prefix}help <commandname> for details]`,
"color": 2563607,
fields : []
};
const sorted = myCommands.array().sort((p, c) => p.help.category > c.help.category ? 1 : p.help.name > c.help.name && p.help.category === c.help.category ? 1 : -1 );
sorted.forEach( c => {
const cat = c.help.category.toProperCase();
if (currentCategory !== cat) {
index = index + 1
embed.fields[index] = {name : `${cat}`,value : ""};
currentCategory = cat;
}
embed.fields[index].value += ` \`${c.help.name}\``;
});
console.log({embed});
message.channel.send({embed});

Categories