I have a heavy array like this:
[
{Id: 1, Name: 'Red', optionName: 'Color'},
{Id: 2, Name: 'Yellow', optionName: 'Color'},
{Id: 3, Name: 'Blue', optionName: 'Color'},
{Id: 4, Name: 'Green', optionName: 'Color'},
{Id: 7, Name: 'Black', optionName: 'Color'},
{Id: 8, Name: 'S', optionName: 'Size'},
{Id: 11, Name: 'M', optionName: 'Size'},
{Id: 12, Name: 'L', optionName: 'Size'},
{Id: 13, Name: 'XL', optionName: 'Size'},
{Id: 14, Name: 'XXL', optionName: 'Size'}
]
What I need to do is to group them by optionName and have two row in the main array like this:
[
{
Name: 'Color',
Data:[{Id: 1, Name: 'Red'},
{Id: 2, Name: 'Yellow'},
{Id: 3, Name: 'Blue'},
{Id: 4, Name: 'Green'},
{Id: 7, Name: 'Black'}]
}, {
Name: 'Size',
Data:[{Id: 8, Name: 'S'},
{Id: 11, Name: 'M'},
{Id: 12, Name: 'L'},
{Id: 13, Name: 'XL'},
{Id: 14, Name: 'XXL'}]
}
]
How to do it in javascript?
This is a snippet I wrote for these kind of situations. You can add this functionality to all of your arrays:
Object.defineProperty(Array.prototype, 'group', {
enumerable: false,
value: function (key) {
var map = {};
this.forEach(function (e) {
var k = key(e);
map[k] = map[k] || [];
map[k].push(e);
});
return Object.keys(map).map(function (k) {
return {key: k, data: map[k]};
});
}
});
You can use it like this. You can just pass a function which defines how you want to group your data.
var newArray = arr.group(function (item) {
return item.optionName;
});
Working Fiddle
If you need, you can replace {key: k, data: map[k]} with {Name: k, Data: map[k]}.
This is also more compact ES6 version of the code above:
Object.defineProperty(Array.prototype, 'group', {
enumerable: false,
value: function (key) {
let map = {};
this.map(e => ({k: key(e), d: e})).forEach(e => {
map[e.k] = map[e.k] || [];
map[e.k].push(e.d);
});
return Object.keys(map).map(k => ({key: k, data: map[k]}));
}
});
Use it like this:
let newArray = arr.group(item => item.optionName))
An ES6 solution using Map object:
function groupBy(arr, key) {
return arr.reduce(
(sum, item) => {
const groupByVal = item[key];
groupedItems = sum.get(groupByVal) || [];
groupedItems.push(item);
return sum.set(groupByVal, groupedItems);
},
new Map()
);
}
var Data = [
{ Id: 1, Name: 'Red', optionName: 'Color' },
{ Id: 2, Name: 'Yellow', optionName: 'Color' },
{ Id: 3, Name: 'Blue', optionName: 'Color' },
{ Id: 4, Name: 'Green', optionName: 'Color' },
{ Id: 7, Name: 'Black', optionName: 'Color' },
{ Id: 8, Name: 'S', optionName: 'Size' },
{ Id: 11, Name: 'M', optionName: 'Size' },
{ Id: 12, Name: 'L', optionName: 'Size' },
{ Id: 13, Name: 'XL', optionName: 'Size' },
{ Id: 14, Name: 'XXL', optionName: 'Size' } ];
document.getElementById("showArray").innerHTML =JSON.stringify([...groupBy(Data, 'optionName')], null, 4);
<pre id="showArray"></pre>
You can use reduce to get the resultset you need:
var result = list.reduce(function(memo, item) {
if (item.optionName === 'Color') {
memo[0].Data.push(
Id: item.Id,
Name: item.Name
});
}
if (item.optionName === 'Size') {
memo[1].Data.push({
Id: item.Id,
Name: item.Name
});
}
return memo;
}, [{ Name: 'Color', Data: [] }, { Name: 'Size', Data: [] }]);
variable list is your first list.
Hope this helps.
This is a snippet I wrote for kind of my situation in my application functionality of all arrays. This snippet code is use in node js application. All the above is is given solution but I was finding some problem in server side in node js.
This snippet is user full me....
var Data= [
{ Id: 1, Name: 'Red', optionName: 'Color' },
{ Id: 2, Name: 'Yellow', optionName: 'Color' },
{ Id: 3, Name: 'Blue', optionName: 'Color' },
{ Id: 4, Name: 'Green', optionName: 'Color' },
{ Id: 7, Name: 'Black', optionName: 'Color' },
{ Id: 8, Name: 'S', optionName: 'Size' },
{ Id: 11, Name: 'M', optionName: 'Size' },
{ Id: 12, Name: 'L', optionName: 'Size' },
{ Id: 13, Name: 'XL', optionName: 'Size' },
{ Id: 14, Name: 'XXL', optionName: 'Size' } ];
function groupBy(arr, key) {
var newArr = [],
types = {},
newItem, i, j, cur;
for (i = 0, j = arr.length; i < j; i++) {
cur = arr[i];
if (!(cur[key] in types)) {
types[cur[key]] = { type: cur[key], data: [] };
newArr.push(types[cur[key]]);
}
types[cur[key]].data.push(cur);
}
return newArr;
}
I use it like this. I just pass a function which defines how you want to group our data.
filterData= groupBy(Data,'optionName');
Result of this snippet of code output.....
[
{"type":"Color","data":[{"Id":1,"Name":"Red","optionName":"Color"},
{"Id":2,"Name":"Yellow","optionName":"Color"},
{"Id":3,"Name":"Blue","optionName":"Color"},
{"Id":4,"Name":"Green","optionName":"Color"},
{"Id":7,"Name":"Black","optionName":"Color"}]},
{"type":"Size","data":[{"Id":8,"Name":"S","optionName":"Size"},
{"Id":11,"Name":"M","optionName":"Size"},
{"Id":12,"Name":"L","optionName":"Size"},
{"Id":13,"Name":"XL","optionName":"Size"},
{"Id":14,"Name":"XXL","optionName":"Size"}]}
]
Show on fiddle
var originalList = [ { Id: 1, Name: 'Red', optionName: 'Color' },
{ Id: 2, Name: 'Yellow', optionName: 'Color' },
{ Id: 3, Name: 'Blue', optionName: 'Color' },
{ Id: 4, Name: 'Green', optionName: 'Color' },
{ Id: 7, Name: 'Black', optionName: 'Color' },
{ Id: 8, Name: 'S', optionName: 'Size' },
{ Id: 11, Name: 'M', optionName: 'Size' },
{ Id: 12, Name: 'L', optionName: 'Size' },
{ Id: 13, Name: 'XL', optionName: 'Size' },
{ Id: 14, Name: 'XXL', optionName: 'Size' } ];
var output = [{ Name: "Color", Data: [] },{ Name: "Size", Data: [] }] ;
originalList.map(function(entry){
if ( entry.optionName === "Color") output[0].Data.push({ Id: entry.Id, Name: entry.Name });
if ( entry.optionName === "Size") output[1].Data.push({ Id: entry.Id, Name: entry.Name });
});
'use strict'
let l = [ { Id: 1, Name: 'Red', optionName: 'Color' },
{ Id: 2, Name: 'Yellow', optionName: 'Color' },
{ Id: 3, Name: 'Blue', optionName: 'Color' },
{ Id: 4, Name: 'Green', optionName: 'Color' },
{ Id: 7, Name: 'Black', optionName: 'Color' },
{ Id: 8, Name: 'S', optionName: 'Size' },
{ Id: 11, Name: 'M', optionName: 'Size' },
{ Id: 12, Name: 'L', optionName: 'Size' },
{ Id: 13, Name: 'XL', optionName: 'Size' },
{ Id: 14, Name: 'XXL', optionName: 'Size' } ];
let color = [];
let size = [];
l.forEach(element => {
if (element['optionName'] === 'Color') {
color.push({'Id': element.Id, 'Name': element.Name});
} else {
size.push({'Id': element.Id, 'Name': element.Name});
}
});
console.log(color);
console.log(size);
You can try this method.
All of the answers lead to the same result, so it all comes down to a personal preference (or company guidelines) on how to tackle this.
// ES5 (traditional javascript) version
function groupByOptionName(list, optionName) {
return list
// filter out any item whose optionName does not match the desired name
.filter(function(item) {
return item.optionName === optionName;
})
// map the item into the desired shape
// (appears to be everything except optionName itself
.map(function(item) {
return {
Id: item.Id,
Name: item.Name
};
})
}
// ES2015/ES6 version
function groupByOptionName(list, optionName) {
return list
// filter out any item whose optionName does not match the desired name
.filter(item => item.optionName === optionName)
// map the item into the desired shape
// (appears to be everything except optionName itself
.map(item => {
Id: item.Id,
Name: item.Name
});
}
This function would let you program the desired result as follows:
var output = [
{Name: 'Color', Data: groupByOptionName(list, 'Color')},
{Name: 'Size', Data: groupByOptionName(list, 'Size')},
];
// the ES2015/ES6 version of this code would replace var with let
While the code itself differs, it is much like the other answers, with only a variation on the steps needed.
One could also opt to leave out any hardcoded option names (Color and Size) by extracting those aswel, this would allow for a more dynamic input, but could also introduce more processing that actually needed.
// ES5 (traditional javascript) version
function getOptionNames(list) {
return list
// map the array into a list of optionNames
.map(function(item) {
return item.optionName;
})
// remove duplicates
.filter(function(item, index, all) {
return all.indexOf(item) === index;
});
}
// ES2015/ES6 version (does exactly the same as the one above)
function getOptionNames(list) {
return list
// map the array into a list of optionNames
.map(item => item.optionName)
// remove duplicates
.filter((item, index, all) => all.indexOf(item) === index);
}
Which allows the result to be fully based on the input data:
// ES5 (traditional javascript) version
var output = getOptionNames(list)
// map the names into the desired structure
.map(function(buffer, name) {
return {
Name: name,
Data: groupByOptionName(list, name)
};
});
// ES2015/ES6 version (does exactly the same as the one above)
var output = getOptionNames(list)
// map the names into the desired structure
.map((buffer, name) => {
Name: name,
Data: groupByOptionName(list, name)
});
By writing all of the data-mangling steps in short consice steps you'd do yourself (especially your future self) a favor if this code ever needs to be adjusted.
If the data set really is heavy (in terms of a lot of data), you must also make sure to keep the number of copies you keep in memory limited. For example, if you never need the original dataset, make sure it can be garbage collected (by not having a variable containing it outside the scope where you receive the data)
Usage:
groupValues([
{ color: 'blue', value: 100 },
{ color: 'blue', value: 75 },
{ color: 'yellow', value: 50 },
{ color: 'yellow', value: 25 }
], 'color')
Result:
[
[{ color: 'blue', value: 100 }, { color: 'blue', value: 75 }],
[{ color: 'yellow', value: 50 }, { color: 'yellow', value: 25 }]
]
Function:
const groupValues = function(arr, key) {
const mapped = {}
arr.forEach(el => {
const actualKey = el[key]
if(!mapped.hasOwnProperty(actualKey)) mapped[actualKey] = []
mapped[actualKey].push(el)
})
return Object.keys(mapped).map(el => mapped[el])
}
Let's say I have an invoice and an invoice item. I'd like to show a list of invoices in a grid on the top and below I want to show the corresponding invoice items to the selected invoice. I have the SQL and JSON part down fine. I query the invoices, query the invoices items for all invoices returned (only 2 queries). Then I match up the items with their invoices. And finally I convert this into JSON. It would look something like this.
{
"success": true,
"results": 2,
"rows": [
{
"data": {"id": 1, "invoiceDate": "2010-01-01", "total": "101.00" },
"invoiceItems": [
{"id": 11, "invoiceID": 1, "item": "baseball", "subtotal": "50.00" },
{"id": 12, "invoiceID": 1, "item": "bat", "subtotal": "51.00" }
]
},
{
"data": {"id": 2, "invoiceDate": "2010-02-02", "total": "102.00" },
"invoiceItems": [
{"id": 21, "invoiceID": 2, "item": "hat", "subtotal": "52.00" },
{"id": 22, "invoiceID": 2, "item": "baseball", "subtotal": "50.00" }
]
}
]
}
So when I select invoice 1 in the top grid, I want to see items 11 and 12 displayed in the botton grid. And then show 21 and 22 when invoice 2 is selected. I'd like to NOT have to return to the server every time I toggle between invoices.
And then finally, I'd love to be able to track which ones have changes so that I can send data back to be persisted.
How is this all possible using Ext JS? I've yet to see a working master detail example using Ext JS.
This is certainly possible with ExtJS and I suggest ExtJS provides tools to help.
However, you might be encountering trouble if you are trying to use a single store to contain your JSON records. I recall reading (I searched for a reference, but was unable to find it) you should think of a store as a single database table rather than trying to store parent/child information in one store.
So, I humbly suggest you store your invoices in one store and your invoice items in a second store, link the child invoice items to the parent invoice via some reference (invoice ID), and use these two stores to support two different grids (or whatever widget) - one for invoices and a second for invoice items. When a user clicks on an invoice, your listener (event handler) would update the invoice items grid/widget appropriately.
This would be my approach.
in that case, you need two readers as code below:
var reader2 = new Ext.data.JsonReader({
root: 'invoiceItems',
fields: [{name: 'id', type:'int'},
{name: 'invoiceID', type:'int'},
{name: 'item', type:'string'},
{name: 'subtotal': type:'float'}]
});
var reader = new Ext.data.JsonReader({
idProperty: 'id',
totalProperty: 'results',
successProperty: "success",
root: 'rows',
fields: [
{name: 'id', type:'int'},
{name: 'invoiceDate', type:'date'},
{name: 'total', type:'float'},
{name: 'invoiceItems', convert: function(v, n){ return reader2.readRecords(n).records;} }//v: value, n: data;
]
});
var conn = new Ext.data.Connection({
timeout : 120000,
url: 'address-path-to-get-json-data',
method : 'POST'
});
var dproxy = new Ext.data.HttpProxy(conn);
var gstore = new Ext.data.Store({
proxy: dproxy,
reader: reader,
sortInfo:{field: 'id', direction: "DESC"}
});
and here is code you need to render the grid
var numrender = function(value, cell, rec, rowIndex, colIndex, store){
if(value*1>0){
return Ext.util.Format.number( value, '0,000.00');
}else return '-';
}
var invoicedetail = function(value, cell, rec, rowIndex, colIndex, store) {
var html = '<div class="itemdetail">{0} - {1} - {2} - {3}</div>';
var re = '';
Ext.each(value,function(item,index){
re += String.format(html,item.get('id'),item.get('invoiceID'),item.get('item'),item.get('subtotal'));
});
return re;
}
var cm = [
new Ext.grid.RowNumberer({header:"No.", width: 30}),
{header: "ID", align: 'left',sortable:true, width: 40, dataIndex: 'id'},
{header: "Invoice Date", align: 'left',sortable:true, width: 40, dataIndex: 'invoiceDate'},
{header: "Total", align: 'right', width: 30, dataIndex: 'total', renderer: numrender},
{header: "Invoice Items", align: 'left',sortable:false, id:'col_detail', width: 100, dataIndex: 'invoiceItems', renderer: invoicedetail}
];
var grid = new Ext.grid.GridPanel({
id:'invoices',
store: gstore,
columns: cm,
enableHdMenu: false,
loadMask: {msg:'Loading Invoices ...'},
enableColumnResize:false,
stripeRows: true,
viewConfig: { autoFill: true },
columnLines : true,
autoExpandColumn: 'col_detail',
renderTo:'grid-wrapper'
});
gstore.load();
or you might be interested in looking at this treegrid:
http://www.max-bazhenov.com/dev/ux.maximgb.tg/index.php
This is certainly possible however you aren't really mapping the sub-objects rather than just expecting them to be there...
Consider this test case (stick it into FireBug and you should see the results..)
var store = new Ext.data.JsonStore({
data: {
success: true, result: [
{
test: {prop1: 1, prop2: 2}
}]
},
root: 'result',
fields: ['test']
});
console.log(store.getRange()[0].data.test.prop1); // prints "1"
In your instance you would do something like this in your row select event...
//assume "this" = your panel containing your Grid (at position 0) and another Grid (at position 1)
var selectedRowRecord = this.get(0).getSelectionModel().getSelected();
var invoiceItemsStore = this.get(1).getStore();
invoiceItemsStore.removeAll();
invoiceItemsStore.loadData(selectedRowRecord.data.invoiceItems);
Hope this helps.
Stuart