Today I tried to use the Grouped DetailsList of the fluent-ui.
What I expected: I need to declare some groups, let's say red, blue, green and then just add to each item, I want to add to the List, a specific property, that maps the Item to the groups.
e.g.:
groups: [
{ key: 'red', name: 'Color: "red"'},
{ key: 'green', name: 'Color: "green"'},
{ key: 'blue', name: 'Color: "blue"' },
],
items: [...,
{ key: 'red',anyProp1: "abc", anyProp2: "dfg",...},
...,
]
What I found out I have to do: I need to sort the Array, which contains my items in that way, that all Items belonging to the Group red need to be in one block. e.g.: [red, red, red, blue, blue, green, green, green]. Now I needed to provide the information about startIndex and count to map my Array of items to the groups.
This is what a definition of a group could look like:
groups: [
{ key: 'groupred0', name: 'Color: "red"', startIndex: 0, count: 2, level: 0 },
{ key: 'groupgreen2', name: 'Color: "green"', startIndex: 2, count: 0, level: 0 },
{ key: 'groupblue2', name: 'Color: "blue"', startIndex: 2, count: 3, level: 0 },
],
I can't understand why they have done it this way (For me it's very inconvenient this way). So, while I'm more between a beginner and an intermediate in JS. I think the guys who implemented this are professionals. There must be a reason. Maybe it has something todo with performance? I could imagine that when it comes to very large lists, it performs better this way, but I'm not sure.
Does anybody knows some details about this and can explain?
Faced the same issue and got a clue here. Then bult my solution.
Following is the function to generate groups array from the given items list sorted by the grouping column:
function groupsGenerator(itemsList, fieldName) {
// Array of group objects
const groupObjArr =[]
// Get the group names from the items list
const groupNames = [...new Set(itemsList.map(item => item[fieldName]))]
// Iterate through each group name to build proper group object
groupNames.forEach(gn => {
// Count group items
const groupLength = itemsList.filter(item => item[fieldName] === gn).length
// Find the first group index
const groupIndex = itemsList.map(item => item[fieldName]).indexOf(gn)
// Generate a group object
groupObjArr.push({
key: gn, name: gn, level: 0, count: groupLength, startIndex: groupIndex
})
})
// The final groups array returned
return groupObjArr
}
Typed and with empty group name option variant of the Timus's answer
function generateGroups(items: any[], fieldName: string, emptyGroupName: string = '-'): IGroup[] {
const groups: IGroup[] = []
const groupNames = [...new Set<string>(items.map(item => item[fieldName]))]
groupNames.forEach(name => {
const groupItemsCount = items.filter(item => item[fieldName] === name).length
const groupStartIndex = items.map(item => item[fieldName]).indexOf(name)
groups.push({
key: name,
level: 0,
name: name ?? emptyGroupName,
count: groupItemsCount,
startIndex: groupStartIndex
})
})
return groups
}
Related
Another question about recursive function, I cant get my head arround them.
I have a list with groups that can have any depth, an example:
{
Id: 1,
Name:"Root",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: []
},
]
},
]
},
{
Id: 2,
Name:"",
Children: []
},
{
Id: 3,
Name:"",
Children: []
},
]
}
I show these groups in a dropdown that the user can select.
What I need to do is when the user clicks on any group, I need to show all users that are a part of that group AND its subgroups.
The information about which users belong to the group and its subgroups is hold by the userlist. That list is flat and every user has an prop that contains an membership array.
I have re-written this method below several times, this is the closest I get, but this more than doubles the expected lenght because I get dublicates.
const getAllUsersInGroup = (group, usersFiltered) => {
if (!group.Children.length) return usersFiltered.flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(
g,
[...usersFiltered, users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id))]
);
});
};
Another test returns almost all but there is missing users on bigger groups with many subgroups.
const getAllUsersInGroup = (group, userss) => {
if (!group.Children.length) return [...userss].flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(g,
users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id)),
);
});
};
I must be stuck in some wrong thinking or just pure stupid..
Maybe I dont need to check the Children lenght and just go thro them all, but as I understand it you need some statment that stops the method.
A little help would be much appreciated!
Regards
I want to populate orders which is an array of type Order. The expected result is orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. How to do so in TypeScript? I am new to it.
export class Product {
constructor(public id: number, public name: string, public price: number) {}
}
export interface Order {
id: number;
qt: number;
}
export const products: Product[] = [
new Product(1, 'Apple', 2.1),
new Product(2, 'Banana', 2.2),
new Product(3, 'Chocolate', 2.3),
new Product(4, 'Dessert', 2.4),
];
export const cart: Product[] = [
products[0],
products[0],
products[2],
products[1],
products[2],
products[0],
products[1],
products[0],
];
export const orders: Order[] = [];
Edit
For those who want to know how
orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}] is obtained.
In the cart:
the quantity of apples (id:1) is qt:4
the quantity of bananas (id:2) is qt:2
the quantity of chocolates (id:3) is qt:2
So by using cart, I have to obtain orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. It should be clear.
Since you're looking for a "LINQ-like" solution, you probably want to use the higher order functions like map/filter/reduce.
Strictly, your problem cannot be solved purely with LINQ projections. Those merely represent map (Select), concatMap/flatMap (SelectMany), and zip (Zip). Your problem involves counting the occurences of each id throughout the entire array.
Pretty much every data manipulation problem can be solved with higher order folds, i.e reduce in javascript land, Aggregate in C# land. This one is no exception. The first thing to do, is to count the occurrences of each id, and build a counter object.
cart.reduce((acc, { id }) => {
acc[id] = (acc[id] ?? 0) + 1;
return acc;
}, {} as Record<number, number>);
Essentially, you start the fold operation with an empty object, then add each id and its occurrence count. Every time an id is encountered in the cart array, you increment its count in the object. If the id doesn't already exist in the accumulating object, nullish coalescing (acc[id] ?? 0) uses 0 and increments that instead.
This will give you-
{ '1': 4, '2': 2, '3': 2 }
Now, you need to turn this into-
[ { id: 1, qt: 4 }, { id: 2, qt: 2 }, { id: 3, qt: 2 } ]
For that, use Object.entries on the fold result to get-
> Object.entries({ '1': 4, '2': 2, '3': 2 })
[ [ '1', 4 ], [ '2', 2 ], [ '3', 2 ] ]
Finally, a simple map is all you need-
Object.entries(...).map(([id, qt]) => ({ id: Number(id), qt }))
Combining all that, you have-
export const orders: Order[] = Object.entries(
cart.reduce((acc, { id }) => {
acc[id] = (acc[id] ?? 0) + 1;
return acc;
}, {} as Record<number, number>)
).map(([id, qt]) => ({ id: Number(id), qt }));
One thing to note here is that Object.entries is pretty inefficient since it builds up an array instead of an iterator. If you're into efficiency, roll an iterator version of Object.entries and use that instead, using generator functions-
function* objEntries<T>(x: Record<string, T>): IterableIterator<[string, T]> {
for (const k in x) {
yield [k, x[k]];
}
}
i have two array data
checked: [
'orange', 'apple', 'juice'
]
and 2nd
products: [
'0': {
title: 'this is product title',
categories: [
'apple', 'juice'
]
}
]
i want to to filter data with computed property with checkbox
i tried this
computed: {
filterProducts(){
return this.products.filter(product => this.checked.includes(product.categories));
}
}
but its not working how can i do this
You can use .every() for that.
Here is an example:
checked = [
'orange', 'apple', 'juice'
]
products = [{
title: 'this is product title',
categories: [
'apple', 'juice'
]
}];
const filteredProducts = products.filter(({ categories }) => categories.every(cat => checked.includes(cat)));
console.log(filteredProducts);
This will return an array of products that have a categories array with all its values included in the checked array.
I'm not sure if this is exactly what you're trying to do, if you instead want to get all the products with a categories array that has at least one of its values in the checked array, use .some() instead of .every().
You're trying to check if an array of strings includes an array (which won't work even if your initial array contained arrays). Instead of using .includes(), use .every() on product.categories to check if all items within this array are contained within checked:
const checked = ['orange', 'apple', 'juice'];
const products = [
{
title: 'this is product title',
categories: ['apple', 'juice']
}
];
const computed = products.filter(product => product.categories.every(item => checked.includes(item) || !checked.length));
console.log(computed);
I have an array with objects that I want to filter according to an indefinite number of conditions passed as parameters.
Here's how I filter the array a with a array of conditions in hide ()
const statuses = [
{
id: 0,
name: 'archived'
},
{
id: 1,
name: 'coming',
hide: (...filterParam) => filterParam.every(rule => rule)
}
];
const filteredStatuses = statuses.filter(element => {
switch (element.id) {
case 1:
return !element.hide(this.isTopTabs());
// other cases with others logic
default:
return true;
}
});
Now if each object can have its own children object array like that:
const statuses = [
{
id: 'statuses',
name: 'treeName',
children: [
{
id: 1,
name: 'inProgress',
hide: (...filterParam) => filterParam.every(Boolean)
},
{
id: 2,
name: 'coming',
checked: false,
hide: (...filterParam) => filterParam.every(Boolean)
}
]
}
];
How I can recursively iterate the same way?
Do you have a better way to filter this array dynamically by avoiding the switch / case?
And finally how to type the rest parameters with a generic like that hide: <T>(...filterParam: Array<T>) => filterParam.every(Boolean) ?
How I can recursively iterate the same way?
const filterRecursively = elements => filterStatuses(elements).map(it => ({ ...it, children: it.children && filterRecursively(it.children) }));
Do you have a better way to filter this array dynamically by avoiding the switch / case?
Why? Whats wrong with the switch case? Isn't it fancy enough?
And finally how to type the rest parameters with a generic like that hide: (...filterParam: Array) => filterParam.every(Boolean) ?
<T>(...filterParam: Array<T>) => boolean
While I was facing slow loading time when it iterate array to render objects, I want to change its data structure. I show table of contents for seasons. When user clicks an item, the item is marked as selected.
Here is current data structure (Array)
const seasons = [{
id: 6,
value: 'All',
}, {
id: 7,
value: 'Spring',
}, {
id: 8,
value: 'Summer',
}, {
id: 9,
value: 'Fall',
}, {
id: 10,
value: 'Winter',
}];
I'm storing selected Season Ids as an Array now
state = {selectedSeasonIds: []}
When selectedSeasonIds has id, I want to remove the id from it. Otherwise, add the id to selectedSeasonIds. (This is current approach)
if(_.includes(this.state.selectedSeasonIds, id)) {
let newSelectedSeasonIds = _.filter(this.state.selectedSeasonIds, (curObject) => {
return curObject !== id;
});
this.setState({selectedSeasonIds : newSelectedSeasonIds});
} else {
let newSelectedSeasonIds = [...this.state.selectedSeasonIds, id];
this.setState({selectedSeasonIds : newSelectedSeasonIds});
}
And here is my pseudo-code for refactoring to convert my arrays to object structure for performance. (I found searching on an object is MUCH faster than searching on the array)
Changing the array to object
const seasons = {
6 :{
id: 6,
value: 'All',
},
7: {
id: 7,
value: 'Spring',
},
8: {
id: 8,
value: 'Summer',
},
9: {
id: 9,
value: 'Fall',
},
10: {
id: 10,
value: 'Winter',
}
};
Changing Selected Seasons <- I want to store only the key(id) of the objects. But I want to use it as an object
state = {selectedSeasonIds : {}} Can I store object type state?
Here is expected logic which can be 50 times faster than array search.
if(selectedSeasonIds[id]) {
//remove
return _.omit(state.selectedSeasonIds, id); < is this right?
} else {
//add
return {...state.selectedSeasonIds, [id]:id} <- Does this look ok?
}
Well if you think this is right, you can copy and paste my code to the answer (I will edit my question too).
Otherwise, Can you provide better suggestion or find the error?
Thank you so much
I guess you have to loop through seasons in order to render them.
My first suggestion is to add selected prop in each one of them so you don't have to check in selectedSeasonsIds on every render.
In case this is not an option, you can still keep the key value approach.
onAdd(id) {
this.setState({
selectedSeasonsIds: {
...this.state.selectedSeasonsIds,
[id]: this.state.selectedSeasonsIds[id] ? false : true
}
})
}
When checking for specific season whether they are selected or not, simply:
render() {
const { seasons, selectedSeasonsIds } = this.state
return (
<div>
...
{Object.keys(seasons).map(key =>
<ItemComponent
{...propsThatYouMightNeed}
selected={selectedSeasonsIds[key]}
/>
)}
</div>
)
}
Maybe something like this? I'd recommend storing arrays and then converting as necessary for lookups.
const seasons = [{
id: 6,
value: 'All',
}, {
id: 7,
value: 'Spring',
}, {
id: 8,
value: 'Summer',
}, {
id: 9,
value: 'Fall',
}, {
id: 10,
value: 'Winter',
}];
const seasonsHash = _.keyBy(seasons, 'id');
// check for existence
const hasId = _.has(seasonsHash, id)
// remove and convert back to array
_.values(_.omit(seasonsHash, id))
// add new id
_.concat(_.values(seasonsHash), id)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>