Related
I have two dropdowns - where each dropdown should filter an objects key. The dropdowns should not exclude each other, or both values from dropdown should work indenpentedly from each other (ie both dropdown values does not need to be true for filtering).
When I select an item from the dropdown, I get one array with two objects, for each dropdown:
[
{
"name": "Type",
"value": [
"calibration"
],
"selected": [
{
"text": "calibration"
}
]
},
{
"name": "Function group",
"value": [
"1 - Test",
"2 - Programming"
],
"selected": [
{
"text": "1 - Test"
}
]
}
]
Above shows two objects, for the two different dropdowns - one with name "type" and one with "Function group".
The "value" in the object above is all of the dropdown items.
"selected" holds the selected item from the dropdown and the filtering should be based on that.In this case we have selected "calibration" and "Test".
The "type" dropdown should filter on the data "category" field while the "function group" should filter on "groupDescription" field. The data that needs to be filtered based on the mentioned keyes and selected values looks like this:
const mockData = [
{
operationDetails: {
id: '5119-03-03-05',
number: '41126-3',
description: 'Clutch wear, check. VCADS Pro operation',
category: 'calibration', //type dropdown
languageCode: 'en',
countryCode: 'GB'
},
functionDetails: {
groupId: 411,
groupDescription: 'Test', //function group dropdown
languageCode: '',
countryCode: ''
},
lastPerformed: '2021-02-22',
time: 20,
isFavorite: false
}
,
{
operationDetails: {
id: '5229-03-03-05',
number: '41126-3',
description: 'Defective brake pad',
category: 'calibration', ///type dropdown
languageCode: 'en',
countryCode: 'GB'
},
functionDetails: {
groupId: 411,
groupDescription: 'Programming', //function group dropdown
languageCode: '',
countryCode: ''
},
lastPerformed: '2020-01-22',
time: 20,
isFavorite: false
}
]
Playground with mock data and response example from dropdown here.
How to filter the data based on the values from the dropdown objects, for each key its responsible for?
It's not the prettiest code, but it does work. The one thing that you'd want to watch out for is the regex. It would be better to not have to parse and do a straight match like category, but if your cases are static then you should be able to figure out if this will work every time. It would also be nice to have a field key in filterDetails so you know which field to try to match in the actual data and you could program that in.
const filterDetails = [
{
name: "Type",
value: ["calibration"],
selected: [
{
text: "calibration",
},
],
},
{
name: "Function group",
value: ["1 - Test", "2 - Programming"],
selected: [
{
text: "Test",
},
],
},
];
const mockData = [
{
operationDetails: {
id: "5119-03-03-05",
number: "41126-3",
description: "Clutch wear, check. VCADS Pro operation",
category: "calibration", //type
languageCode: "en",
countryCode: "GB",
},
functionDetails: {
groupId: 411,
groupDescription: "Test", //function group
languageCode: "",
countryCode: "",
},
lastPerformed: "2021-02-22",
time: 20,
isFavorite: false,
},
{
operationDetails: {
id: "5229-03-03-05",
number: "41126-3",
description: "Defective brake pad",
category: "calibration", ///type
languageCode: "en",
countryCode: "GB",
},
functionDetails: {
groupId: 411,
groupDescription: "Programming", //function group
languageCode: "",
countryCode: "",
},
lastPerformed: "2020-01-22",
time: 20,
isFavorite: false,
},
];
console.log(
"filtered mockData: ",
mockData.filter(({ operationDetails, functionDetails }) => {
let groupDescriptionMatch = false;
let categoryMatch = false;
for (const details of filterDetails) {
if (
details.name === "Type" &&
details.selected[0].text === operationDetails.category
)
categoryMatch = true;
if (details.name === "Function group") {
let parsedGroup = details.selected[0].text.match(/[a-zA-Z]+/g);
if (parsedGroup[0] === functionDetails.groupDescription) {
groupDescriptionMatch = true;
}
}
}
return groupDescriptionMatch && categoryMatch;
})
);
I'm looking for a solution to dynamicly watch the values of an array with objects in vue:
new Vue({
el: "#app",
data: {
options: [
{"id": 1, "title": "One", "value": false},
{"id": 2, "title": "Two", "value": false },
{"id": 3, "title": "Three", "value": false}
]
},
I tried different solutions with watch and methods but none worked properly. How can I watch the objects in "options" for change of "value" and limit the maximum number of true objects to a number in this case 1 for example. If I set 2nd object true the first object need to set to false so that only the last changed object is true?
A computed property does the trick:
...
data: () => ({
options: [...], // your current options
trueValuesLimit: 2 // If you want to set the limit as data a property
})
computed: {
// If limitOn is true, then at least one value property is true
limitOn() {
const count = this.options.reduce((acc, option) => {
// If value is true, then add one to the accumulator
option.value ? acc++ : null
return acc
} , 0)
// Limit sets to 1, update as required
return count >= 1
// You can use a data property to validate the limit
// return count >= this.trueValuesLimit
}
}
...
Now you can use limitOn property to enable/disable the input to select/deselect options.
You just need to update the options, like this (in case of limit: 1):
new Vue({
el: "#app",
data: {
limit: 1, // this is not used in this snippet!
options: [{
"id": 1,
"title": "One",
"value": false
},
{
"id": 2,
"title": "Two",
"value": false
},
{
"id": 3,
"title": "Three",
"value": false
}
]
},
methods: {
setToTrue(id) {
// creating a copy of the options data object
let options = JSON.parse(JSON.stringify(this.options))
this.options = options.map(e => {
const value = e.id === id ? true : false
return { ...e, value }
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="option in options" :key="option.id">
ID: {{ option.id }}<br />
Title: {{ option.title }}<br />
Value: <span style="font-weight: 700; cursor: pointer;" #click="setToTrue(option.id)">{{ option.value }}</span><br />
<hr />
</div>
</div>
For a higher limit (like you'd allow 2 or more true values), you'd need to come up with the ruleset about which ones to keep (beside the new one) - the strategy (ruleset) could be FIFO, LIFO, etc.
I'm trying to filter this objects array and keep the original one aside.
{"departments":
[
{
“name": “AAA",
“selected”: true,
"courses": [
{
"name": “course1",
“selected”: true,
“titles”:
[{
"name": “title1",
“selected”: true
},
{
"name": “title2",
“selected”: false
}]
},
{
"name": “course2",
“selected”: false,
“titles”:
[{
"name": “title1",
“selected”: false
}]
}
]
},
{
“name": “BBB",
“selected”: false,
"courses": [{...}]
{...}
]
}
I want to find all the selected departments, courses and titles. And it should be in the same format.
I tried with below code, but it change original data. I want to keep that aside too.
const depts = departments.filter((dept: any) => {
if (dept.selected) {
dept.courses = dept.courses.filter((course: any) => {
if (course.selected) {
if (course.titles) {
course.titles = course.titles.filter(({selected}: any) => selected);
}
return true;
}
return false;
});
return true;
}
return false;
});
What would be considered the best solution in this case?
Shorter alternative can be to use the JSON.parse reviver parameter :
var arr = [{ name: "AAA", selected: true, courses: [{name: "course1", selected: true, titles: [{ name: "title1", selected: true }, { name: "title1", selected: false }]}, { name: "course2", selected: false, titles: [{ name: "title1", selected: false }]}]}]
var result = JSON.parse(JSON.stringify(arr), (k, v) => v.map ? v.filter(x => x.selected) : v)
console.log( result )
your filtering logic seems to be correct. only problem is that code changes original array. in order to overcome this problem just create a deep clone of original array and run filtering logic on it
filterArray() {
const clone = JSON.parse(JSON.stringify(this.departments));
const depts = clone.filter((dept: any) => {
if (dept.selected) {
dept.courses = dept.courses.filter((course: any) => {
if (course.selected) {
if (course.titles) {
course.titles = course.titles.filter(({ selected }: any) => selected);
}
return true;
}
return false;
});
return true;
}
return false;
});
console.log(depts);
}
here is a demo https://stackblitz.com/edit/angular-xx1kp4
const filterSelected = obj => {
return {
...obj,
departments: obj.departments.map(dep => {
return {
...dep,
courses: dep.courses.map(course => {
return {
...course,
titles: course.titles.filter(title => title.selected),
};
}).filter(course => course.selected),
};
}).filter(dep => dep.selected),
};
}
const all = {
departments: [
{
name: "AAA",
selected: true,
courses: [
{
name: "course1",
selected: true,
titles: [
{
name: "title1",
selected: true
}, {
name: "title1",
selected: false
}
]
}, {
name: "course2",
selected: false,
titles: [
{
name: "title1",
selected: false
}
]
},
]
}
]
};
console.log(filterSelected(all));
I don't know if you prefer an API false. Here is my tip:
You can to use an API Json Server.
Install JSON Server
npm install -g json-server
Create a db.json file with some data
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
Start JSON Server
json-server --watch db.json
Now if you go to http://localhost:3000/posts/1, you'll get
{ "id": 1, "title": "json-server", "author": "typicode" }
you can search your array of objects using various shapes and it will come filtered. More about the API here: https://github.com/typicode/json-server
(Use a filter to do your searches on the Angular, it will bring you right what you need, use a method inside your component)
First Array
const firstArray = [
{
"value": "306",
"label": "All"
},
{
"value": "316",
"label": "Tips"
},
{
"value": "315",
"label": "News"
},
{
"value": "32",
"label": "Jobs"
}]
Second Array
const secondArray = [
{
name: "name",
description: "desc",
image: "path_image",
culture: [{
name: "name",
value: "32"
}]
},
{
name: "name",
description: "desc",
image: "path_image",
culture: [{
name: "name",
value: "32"
}]
}];
Trying to filter my firstArray with only keeping the object with the value corresponding to 32.
Still learning Javascript and it's in a React Native Project. They changed some info of the API and it was working when I only had : culture":"32"
Code :
let newOrigin = [...new Set(this.state.secondArray.map(product => product.culture))],
visibleOrigin = firstArray.filter(item => newOrigin.includes(item.value));
this.setState({ displayOrigin: visibleOrigin });
How to get the value inside the array culture.
Any advice, any help ? Thank you.
So. I found a solution to my issue. Here's the code :
_nameFunction = () => {
let filteredSecondArray = [...new Set(this.state.secondArray.map(o => o.culture[0].value))];
this.setState({
firstArray: this.state.firstArray.filter(item => filteredSecondArray.includes(item.value))
})
}
I am trying to create a category tree using the array of json objects below.
I want to set a category as a child of another category if its parent equals the id of the other, and I want the posts also to be a children of that category instead of having a separate field for posts, I'll add a flag field that if it is a category or not isParent.
It looks like its working alright, but as you may see, if a category has both category and post as child, it'll only show the categories. Another problem with that is if the post has a null value on its array, it will still push them as children.
What are the mistakes in my code, or is there a simpler or better solution to this?
var tree = unflatten(getData());
var pre = document.createElement('pre');
console.log(tree);
pre.innerText = JSON.stringify(tree, null, 4);
document.body.appendChild(pre);
function unflatten(array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: 0
};
_.map(array, function(arr) {
_.set(arr, 'isParent', true);
});
var children = _.filter(array, function(child) {
return child.parent == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == 0) {
tree = children;
} else {
parent['children'] = children;
}
_.each(children, function(child) {
var posts = _.map(child.posts, function(post) {
return _.set(post, 'isParent', false);
});
child['children'] = posts;
delete child.posts;
unflatten(array, child);
});
}
return tree;
}
function getData() {
return [{
"id": "c1",
"parent": "",
"name": "foo",
"posts": [{
"id": "p1"
}]
}, {
"id": "c2",
"parent": "1",
"name": "bar",
"posts": [{
"id": "p2"
}]
}, {
"id": "c3",
"parent": "",
"name": "bazz",
"posts": [
null
]
}, {
"id": "c4",
"parent": "3",
"name": "sna",
"posts": [{
"id": "p3"
}]
}, {
"id": "c5",
"parent": "3",
"name": "ney",
"posts": [{
"id": "p4"
}]
}, {
"id": "c6",
"parent": "5",
"name": "tol",
"posts": [{
"id": "p5"
}, {
"id": "p6"
}]
}, {
"id": "c7",
"parent": "5",
"name": "zap",
"posts": [{
"id": "p7"
}, {
"id": "p8"
}, {
"id": "p9"
}]
}, {
"id": "c8",
"parent": "",
"name": "quz",
"posts": [
null
]
}, {
"id": "c9",
"parent": "8",
"name": "meh",
"posts": [{
"id": "p10"
}, {
"id": "p11"
}]
}, {
"id": "c10",
"parent": "8",
"name": "ror",
"posts": [{
"id": "p12"
}, {
"id": "p13"
}]
}, {
"id": "c11",
"parent": "",
"name": "gig",
"posts": [{
"id": "p14"
}]
}, {
"id": "c12",
"name": "xylo",
"parent": "",
"posts": [{
"id": "p15"
}]
}, {
"id": "c13",
"parent": "",
"name": "grr",
"posts": [{
"id": "p16"
}, {
"id": "p17"
}, {
"id": "p14"
}, {
"id": "p18"
}, {
"id": "p19"
}, {
"id": "p20"
}]
}]
}
<script src="//cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
Expected Output
So the expected output will be more like:
[
{
id: 'c1',
isParent: true,
children: [
{
id: 'c2',
isParent: true,
children: []
},
{
id: 'p1'
isParent: false
}
]
}
]
And so on..
Your code is very imperative. Try focusing on the "big picture" of data flow instead of writing code by trial-and-error. It's harder, but you get better results (and, in fact, usually it's faster) :)
My idea is to first group the categories by their parents. This is the first line of my solution and it actually becomes much easier after that.
_.groupBy and _.keyBy help a lot here:
function makeCatTree(data) {
var groupedByParents = _.groupBy(data, 'parent');
var catsById = _.keyBy(data, 'id');
_.each(_.omit(groupedByParents, ''), function(children, parentId) {
catsById['c' + parentId].children = children;
});
_.each(catsById, function(cat) {
// isParent will be true when there are subcategories (this is not really a good name, btw.)
cat.isParent = !_.isEmpty(cat.children);
// _.compact below is just for removing null posts
cat.children = _.compact(_.union(cat.children, cat.posts));
// optionally, you can also delete cat.posts here.
});
return groupedByParents[''];
}
I recommend trying each part in the developer console, then it becomes easy to understand.
I have made a small fidde that I think that is what you want.
http://jsfiddle.net/tx3uwhke/
var tree = buildTree(getData());
var pre = document.getElementById('a');
var jsonString = JSON.stringify(tree, null, 4);
console.log(jsonString);
pre.innerHTML = jsonString;
document.body.appendChild(pre);
function buildTree(data, parent){
var result = [];
parent = typeof parent !== 'undefined' ? parent : {id:""};
children = _.filter(data, function(value){
return value.parent === parent.id;
});
if(!_.isEmpty(children)){
_.each(children, function(child){
if (child != null){
result.push(child);
if(!_.isEmpty(child.posts)){
var posts = _.filter(child.posts, function(post){
return post !== null && typeof post !== 'undefined';
});
if(!_.isEmpty(posts)){
_.forEach(posts, function(post){
post.isParent = false;
});
}
result = _.union(result, posts);
delete child.posts;
}
ownChildren = buildTree(data, child);
if(!_.isEmpty(ownChildren)){
child.isParent = true;
child.children = ownChildren;
}else{
child.isParent = false;
}
}
});
}
return result;
}
EDIT: made a new fiddle to contain the isParent part you can find it here
While this problem looks simple, I can remember to have struggled achieving it in a simple way. I therefore created a generic util to do so
You only have to write maximum 3 custom callbacks methods.
Here is an example:
import { flattenTreeItemDeep, treeItemFromList } from './tree.util';
import { sortBy } from 'lodash';
const listItems: Array<ListItem> = [
// ordered list arrival
{ id: 1, isFolder: true, parent: null },
{ id: 2, isFolder: true, parent: 1 },
{ id: 3, isFolder: false, parent: 2 },
// unordered arrival
{ id: 4, isFolder: false, parent: 5 },
{ id: 5, isFolder: true, parent: 1 },
// empty main level folder
{ id: 6, isFolder: true, parent: null },
// orphan main level file
{ id: 7, isFolder: false, parent: null },
];
const trees = treeItemFromList(
listItems,
(listItem) => listItem.isFolder, // return true if the listItem contains items
(parent, leafChildren) => parent.id === leafChildren.parent, // return true if the leaf children is contained in the parent
(parent, folderChildren) => parent.id === folderChildren.parent // return true if the children is contained in the parent
);
console.log(trees);
/*
[
{
children: [
{
children: [{ data: { id: 3, isFolder: false, parent: 2 }, isLeaf: true }],
data: { id: 2, isFolder: true, parent: 1 },
isLeaf: false,
},
{
children: [{ data: { id: 4, isFolder: false, parent: 5 }, isLeaf: true }],
data: { id: 5, isFolder: true, parent: 1 },
isLeaf: false,
},
],
data: { id: 1, isFolder: true, parent: null },
isLeaf: false,
},
{ children: [], data: { id: 6, isFolder: true, parent: null }, isLeaf: false },
{
data: {
id: 7,
isFolder: false,
parent: null,
},
isLeaf: true,
},
]
*/
I did not check with your example as all cases are different, you however need to implement only 3 methods to let the algorithm build the tree for you:
If the item is a folder or a leaf (in your case just check if the children contain any non falsy item) i.e. listItem.posts.some((value)=>!!value)
if a parent contains the leaf child, (parent, child) => !!parent.posts.filter((val)=>!!val).find(({id})=>child.id === id)
if a parent contains the folder: optional if this is the same logic as for a leaf child.