Optgroup with ngFor [duplicate] - javascript

I'm working on a project right now that makes use of plenty of information on selects.
Is there any way to group this information using native Angular (5/6?) functions?
I tried the ng-select component but did not perform well.
My :
<div class="form-group">
<label for="disbursementType" class="ng-required">Typ wypłaty</label>
<select id="disbursementType" class="form-control"
name="disbursementType" required [(ngModel)]="hero.power" #power="ngModel" >
<option *ngFor="let disbursementTypeOption of disbursementTypeOptions"
[value]="disbursementTypeOption.key">{{disbursementTypeOption.title}}</option>
</select>
</div>
And this is my class variable
disbursementTypeOptions = [
{
"key": 1,
"title": "Przelew na konto",
"group": "first",
},
{
"key": 2,
"title": "Czek Giro",
"group": "second",
},
];

You could slice and dice and array to grouped object inside your component, and then use optgroup with option tag inside select
HTML
<select id="disbursementType" class="form-control"
name="disbursementType" required [(ngModel)]="hero.power" #power="ngModel" >
<optgroup [attr.label]="group.name" *ngFor="let group of groupedArray">
<option *ngFor="let disbursementTypeOption of group.values"
[value]="disbursementTypeOption.key">
{{disbursementTypeOption.title}}
</option>
</optgroup>
</select>
Code
unique: string[];
groupedArray: any[]
formatArray() {
this.unique = [...new Set(this.disbursementTypeOptions.map(item => item.group))];
this.groupedArray = unique.map(i => ({
name: i,
values: disbursementTypeOptions.filter(d => d.group === i)
}))
}

If you don't want to add lodash to use groupBy, you can use rxjs (since it already comes with angular) like below. Not really useful if you have static data (why not group it in the 1st place), but can be useful with async data.
const disbursementTypeOptions: Item[] = [
{
'key': 1,
'title': 'Przelew na konto',
'group': 'first',
},
{
'key': 2,
'title': 'Czek Giro',
'group': 'second',
},
];
let groups: { group: string, items: Item[] }[];
from(disbursementTypeOptions)
.pipe(
groupBy(v => v.group),
mergeMap(group => group.pipe(toArray(), map(items => ({ group: group.key, items })))),
toArray()
)
.subscribe(grouped => groups = grouped);

Related

Compare Two values in Javascript then map

I have Different data from different Schema. I want to compare data from both schema to find out if they are same then map through them.
allStaff = [
{
role: "admin",
name: "jamal",
branch: {
name: "kansas",
},
},
{
role: "legal",
name: "keith",
branch: {
name: "arizona",
},
},
{
role: "admin",
name: "dwyane",
branch: {
name: "kansas",
},
},
...
];
contributor = {
fullName: 'Aaraf',
branch: {
name: "kansas",
},
};
I want the form option for this contributor to contain only staff in same branch as he is(kansas).
<Form.Group controlId="branch">
<Form.Label>Investment Officer:</Form.Label>
<Form.Control
as="select"
value={investmentOfficer}
onChange={(e) => setInvestmentOfficer(e.target.value)}
>
<option>Investment Officer</option>
{(allStaff.branch?.name === contributor.branch?.name).map(
staff) => (
<option key={staff._id} value={staff._id}>
{staff.name}
</option>
)
)}
</Form.Control>
</Form.Group>
You want to filter your data first before you map it so that allStaff is filtered down where the branch names match. I have provided the code to do so below.
allStaff.filter((staff) => staff.branch.name === contributor.branch.name)
.map((filteredStaff) => (
<option key={staff._id} value={staff._id}>
{staff.name}
</option>
));
the problem is your code trying to map a boolean... .map() is built in attribute for array, also your code not explaining what is you trying to iterate, so the first step figure out which array you want to iterate first, after that follow this example,
for this example, im going to assume that you want to iterate allStaff.branch
{allStaff.branch.map(
(branch,i) => {
if(branch?.name === contributor.branch[i].name)
return (<option key={branch._id} value={branch._id}>
{branch.firstName}
</option>)
})}

return array of objects from props React JS

so I have the following component that is a dropdown list created using react-select.
import React from 'react'
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
];
class MealsFilters extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
};
}
handleChange = (selectedOption) => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
}
render() {
const { selectedOption } = this.state;
return (
<div className="container my-3">
<div className="row">
<div className="col-lg-4 col-md-6 col-sm-8">
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
</div>
</div>
</div>
)
}
}
export default MealsFilters;
the options variable is the default one from the docs. I actually need to replace its values by each meal category available.
To do so, as you can see, I need to create an array of objects with a value and a label.
this component accesses meal categories through props called meals that are like so:
console.log(this.props.meals);
=> [{
id: 0,
name: spaghettis,
category: italian,
price: 5.99},
{
id: 1,
name: hamburger,
category: american,
price: 7.99},
{
etc.
}, {}]
How can I take advantage of this.props.meals to get my options array of objects ?
EDIT: multiple meals can have the same category, and I need each category to only appear once in the options.
Map over your this.props.meals array, and create the needed options array,
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={this.props.meal.map(item=>({value: item.id, label: item.name}))}
/>
You could do something like this:
options={this.props.meals.map(
({id, name})=>({value:id,label:name})
)}
You could also use redux connect to create a container that will map the data to dropdown values for you
You can merge the data by category in the following way:
var items = [
{
id: 0,
name: 'spaghettis',
category: 'italian',
price: 5.99,
},
{
id: 1,
name: 'hamburger',
category: 'american',
price: 7.99,
},
{
id: 2,
name: 'other hamburger',
category: 'american',
price: 7.99,
},
];
console.log(
[
...items.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value })),
);
In the component it'll look like this:
options={[
...this.props.meals.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value }))}
You only need the "name" property so when you map through meals, simply retrieve it. Then upper case the first letter.
const meals = [{
id: 0,
name: "spaghettis",
category: "italian",
price: 5.99
},
{
id: 1,
name: "hamburger",
category: "american",
price: 7.99
}
]
const result = meals.map(({name}) => ({
label: `${name[0].toUpperCase()}${name.slice(1)}`,
value: name
}))
console.log(result);
You can use getOptionLabel and getOptionValue props.
<Select
options={this.props.meals},
getOptionLabel={m => m.name}
getOptionValue={m => m.id} />
https://react-select.com/props
getOptionLabel generic = (option) => string
Resolves option data to a string to be displayed as the label by components
getOptionValue generic = (option) => string
Resolves option data to a string to compare options and specify value attributes

Dynamic array filtering by object property

I have a react live search dropdown component that filters through an array of objects by a search term. It filters my objects by title and then returns a list of all the related objects. This works fine.
Current:
Data Structure
data: [
{ id: 1, title: 'Some title here' },
{ id: 2, title: 'Another title' },
{ id: 3, title: 'last title' },
]
Component
<LiveSearch
term={term}
data={data} />
Inside Live search component
Filter data by term and render list
return data
.filter(item => item.title.toLowerCase().includes(term.toLowerCase())
.map((item, idx) => <li key={idx}>{item.title}</li>
My objects to search by are getting more advanced and what I would like to be able to do is pass into my component an array of property names I would like to compare to the search term.
My thinking process behind it is to loop through the object properties and if on of the properties matches the term the loop breaks and returns true adding that object to the list of items to be displayed.
Goal
Data Structure
data: [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
]
Component
<LiveSearch
searchFields={['country', 'title']}
term={term}
data={data} />
Inside Component filtering
return data
.filter(item => {
// Dynamic filtering of terms here
})
.map((item, idx) => <li key={idx}>{item.title}</li>
Inside the filter I'm trying to get a loop through the array and dynamically produce logic similar to this
item.searchFields[0].toLowerCase().includes(term.toLowerCase()) ||
item.searchFields[1].toLowerCase().includes(term.toLowerCase())
But obviously could loop over an infinite number of searchfields/properties
Use Array#some()
Something like
term = term.toLowerCase()
return data
.filter(item => {
return searchFields.some(field => item[field].toLowerCase().includes(term))
}).map(...
Check if some of the searchFields match:
// Checks wether a value matches a term
const matches = (value, term) => value.toLowerCase().includes(term.toLowerCase());
// Checks wether one of the fields in the item matcues the term
const itemMatches = (fields, term) => item => fields.some(field => matches(item[field], term);
// Filter the data to only contain items where on of the searchFields matches the term
const result = props.data.filter( itemMatches(props.searchFields, props.term) );
return result.map(item => <li key={idx}>{item.title}</li>);
You can use Array .some combined with .filter
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
let data = [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
], searchFields = ["country", "title"], term = "canada";
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
console.log(result);

return multiple elements in array.map / Subcategories in dropdown

I'm using array.map to loop through a nested array.
indicatorCategories is my first array which contains a name and id, and an nested array named indicators.
I'm mapping these indicators to a dropdown in react-bootstrap, but I also have to map the name of the category.
Currently this is the code I have.
{indicatorCategories.map(indicatorCategory => {
return (
(
<option
disabled
key={indicatorCategory.id}
value={indicatorCategory.id}
>
{"---" + indicatorCategory.name + "---"}
</option>
),
indicatorCategory.indicators.map(indicator => {
return (
<option key={indicator.id} value={indicator.id}>
{indicator.name}
</option>
);
})
);
})}
The result is that I'm only mapping the indicators, the indicatorCategories do not show up. I guess array.map isn't ment to return an element and map over a nested array? Or am I just making a stupid syntax error? Been wrestling with it for a while but no cigar.
If it's not possible, what are the alternatives here? I prefer not using some hover over categories to show the indicators from that category since it isn't very mobile friendly.
Thanks.
You can do what you need to do with a combination of reduce and map. Here's an example with dummy data, you transfer it to react.
let data = [
{
id: 1,
value: 'a',
data: [
{id: 11, value: 'b'}
]
},
{
id: 2,
value: 'aa',
data: [
{id: 12, value: 'bb'},
{id: 12, value: 'bb'},
]
}
]
let results = data.reduce((acc, cur) => {
acc = acc.concat(cur.value);
return acc.concat(
cur.data.map((nested) => {
return nested.value;
})
)
}, []);
console.log(results)
https://jsfiddle.net/avLvxsb6/
As you can see in results you have all the values from the array and the nested arrays. Another alternative is to map the data and then flatten the results so you don't have nested arrays. Then you loop through that and render

ngOptions two level object display

I have this structure:
model = [
{
name: 'name1',
items: [
{
name: 'subobj1'
},
{
name: 'subobj2'
}]
},
{
name: 'name2',
items: [
{
name: 'subobj1'
},
{
name: 'subobj2'
}]
},
....
]
Question is: How do I write ngOptions attrbiute to output this object like this?:
<select>
<optgroup label="name1">
<label>subobj1</label>
<label>subobj2></label>
</optgroup>
....
</group>
Also - ngRepeat is not an option. I have to do this ngOptions alone for plugin being used to work.
ngOptions doesn't support multi-dimensional arrays. You must flatten your array first.
Read more in this answer.
I used a filter:
app.filter('flatten' , function(){
return function(array){
return array.reduce(function(flatten, group){
group.items.forEach(function(item){
flatten.push({ group: group.name , name: item.name})
})
return flatten;
},[]);
}
})
And the markup:
<select ng-model="select"
ng-options="item.name
group by item.group
for item in model | flatten"></select>
<select>
<option ng-repeat-start="m in model" ng-bind="m.name"></option>
<option ng-repeat-end ng-repeat="item in m.items" ng-bind="item.name"></option>
</select>
You might add something like style="font-weight: bold;" on the first option (which is the group label, by the way) and something like style="padding-left: 15px;" on the second option line, which is another repeat for all the first option line.
So basically by doing this you just add 2 levels (without optgroup tag, mind you) to your select.

Categories

Resources