I am having a hard time doing an Angular filter to solve a problem as below.
The filter logic is as below:
1) If all listItem of that item has qtyLeft != 0, do not display that item
2) If any of the listItem of that item has qtyLeft == 0, display the item title as well as coressponding listItem that have qtyLeft == 0
Here's a basic example of my data structure, an array of items:
$scope.jsonList = [
{
_id: '0001',
title: 'titleA',
list: {
listName: 'listNameA',
listItem: [
{
name: 'name1',
qtyLeft: 0
},
{
name: 'name2',
qtyLeft: 0
},
]
}
},
{
_id: '0002',
title: 'titleB',
list: {
listName: 'listNameB',
listItem: [
{
name: 'name3',
qtyLeft: 2
},
{
name: 'name4',
qtyLeft: 0
},
]
}
},
{
_id: '0003',
title: 'titleC',
list: {
listName: 'listNameC',
listItem: [
{
name: 'name5',
qtyLeft: 2
},
{
name: 'name6',
qtyLeft: 2
},
]
}
},
]
Here is the final expected outcome:
<div ng-repeat="item in jsonList | filter: filterLogic">
<div> </div>
</div>
// final outcome
<div>
<div>Title: titleA, ListItem: Name1, Name2</div>
<div>Title: titleB, ListItem: Name4</div>
</div>
Created working Plunkr here. https://plnkr.co/edit/SRMgyRIU7nuaybhX3oUC?p=preview
Do not forget to include underscore.js lib in your project if you are going to use this directive.
<div ng-repeat="jsonItem in jsonList | showZeroElement track by $index">
<div>Title:{{ jsonItem.title}}, ListItem:<span ng-repeat="item in
jsonItem.list.listItem track by $index" ng-if="item.qtyLeft==0">
{{item.name}}</span>
</div>
</div>
And
app.filter('showZeroElement', function() {
return function(input) {
var items = []
angular.forEach(input, function(value, index) {
angular.forEach(value.list.listItem, function(val, i) {
var found = _.findWhere(items, {
'title': value.title
})
if (val.qtyLeft === 0 && found === undefined) {
items.push(value)
}
})
})
return items
}
})
Related
<div>
<q-card
v-for="box in boxes"
:key="box.id">
<q-item>
<q-item-section>
<span> {{ box.name }} </span>
</q-item-section>
</q-item>
<q-list>
<q-item
v-for="tool in box.tools"
:key="tool.id"
clickable
<q-item-section>
<span> {{ tool.name }} </span>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
Form input filter value
inputFilterValue = "box A"
Filter boxes
Edited with return.
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}
This works
How to filter too nested v-for box-tools list?
EDITED:
CODEPEN: https://codepen.io/ijose/pen/NWyoRMX
You can use JavaScript filter() along with some() method. some() method checks if any of the elements in an array pass the function.
Demo :
new Vue({
el: '#app',
data: {
value: null,
boxes: [],
dataObj: [{
id: 1,
name: 'Box A',
tools: [{
id: 1,
name: 'Tool A'
}, {
id: 2,
name: 'Tool D2'
}]
}, {
id: 2,
name: 'Box B',
tools: [{
id: 1,
name: 'Tool B'
}]
}, {
id: 3,
name: 'Box C',
tools: [{
id: 1,
name: 'Tool C'
}]
}]
},
mounted() {
this.boxes = structuredClone(this.dataObj);
},
methods: {
inputFilterValue(filterField) {
if (filterField === 'box') {
this.boxes = this.dataObj.filter(box => box.name.toLowerCase().match(this.value.toLowerCase()))
} else {
const filteredToolsArr = this.dataObj.map(box => {
const filteredTool = box.tools.filter((
{ name }) => name.toLowerCase().match(this.value.toLowerCase()));
return { ...box, tools: filteredTool }
})
this.boxes = filteredToolsArr.filter(obj => obj.tools.length);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Filter : <input type="text" v-model="value" #keyup="inputFilterValue('tool')"/>
<ul v-for="box in boxes" :key="box.id">
<li>{{ box.name }}</li>
<ul v-for="tool in box.tools" :key="tool.id">
<li>{{ tool.name }}</li>
</ul>
</ul>
</div>
In the filter callback of boxes add a condition if the tool name matches the inputFilterValue
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase()) ||
box.tools.filter(tool=>tool.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}
I have a complex nested JSON Array and I want to filter it(name property) through based on what user enters in input tag and show it as an autocomplete. A basic of it I have created here on stackblitz click here for the code. I have two entries of name "Tom" in two different objects so when user types Tom it should appear twice in the autocomplete as of now it shows only once. So if I press letter "T" it should show me all the names starting with "T". Here in this case "Tom" twice if I press "To" and if I press just "T" then Tiffany and Tom 2 times. Could you please help me here in the code ?
Any help is appreciated. Thanks much!
You can check the below code also, I have updated code you check in stackbliz https://stackblitz.com/edit/angular-ivy-k14se7
matches = [];
ngOnInit() {}
searchKeyup(ev) {
var term: any = ev.target as HTMLElement;
console.log("Value", term.value);
this.matches = [];
let content = [
{
name: 'Robert',
children: [],
},
{
name: 'Doug',
children: [
{
name: 'James',
children: [
{
name: 'John',
children: [
{
name: 'Tom',
children: [],
},
],
},
],
},
],
},
{
name: 'Susan',
children: [
{
name: 'Tiffany',
children: [
{
name: 'Merry',
children: [
{
name: 'Sasha',
children: [],
},
{
name: 'Tommy',
children: [],
},
],
},
],
},
],
},
];
if(term.value.length > 0){
this.filter(content, term.value);
} else {
document.getElementById('list').innerHTML = '';
}
if (this.matches.length > 0) {
document.getElementById('list').innerHTML = this.matches.map(match => match.name).join(",");
} else{
document.getElementById('list').innerHTML = "";
}
}
filter(arr, term) {
arr.forEach((i) => {
if (i.name.includes(term)) {
this.matches.push(i);
}
if (i.children.length > 0) {
this.filter(i.children, term);
}
});
console.log(this.matches);
}
You were on a good path. The only missing thing in this recursive walk is keeping state of matches like this:
filter(arr, term, matches) {
if (term.length == 0) {
matches = [];
document.getElementById('list').innerHTML = '';
}
arr.forEach((i) => {
if (i.name.includes(term)) {
matches.push(i);
}
if (i.children.length > 0) {
this.filter(i.children, term, matches);
}
});
console.log(matches);
if (matches.length > 0) {
document.getElementById('list').innerHTML = matches[0].name;
}
}
I have an n levels deep nested array of tag objects with title and ID. What I'm trying to create is a an object with IDs as keys and values being an array describing the title-path to that ID.
I'm no master at recursion so my attempt below doesn't exactly provide the result I need.
Here's the original nested tag array:
const tags = [
{
title: 'Wood',
id: 'dkgkeixn',
tags: [
{
title: 'Material',
id: 'ewyherer'
},
{
title: 'Construction',
id: 'cchtfyjf'
}
]
},
{
title: 'Steel',
id: 'drftgycs',
tags: [
{
title: 'Surface',
id: 'sfkstewc',
tags: [
{
title: 'Polished',
id: 'vbraurff'
},
{
title: 'Coated',
id: 'sdusfgsf'
}
]
},
{
title: 'Quality',
id: 'zsasyewe'
}
]
}
]
The output I'm trying to get is this:
{
'dkgkeixn': ['Wood'],
'ewyherer': ['Wood', 'Material'],
'cchtfyjf': ['Wood', 'Construction'],
'drftgycs': ['Steel'],
'sfkstewc': ['Steel', 'Surface'],
'vbraurff': ['Steel', 'Surface', 'Polished'],
'sdusfgsf': ['Steel', 'Surface', 'Coated'],
'zsasyewe': ['Steel', 'Quality']
}
So I'm building this recursive function which is almost doing it's job, but I keep getting the wrong paths in my flat/key map:
function flatMap(tag, acc, pathBefore) {
if (!acc[tag.id]) acc[tag.id] = [...pathBefore];
acc[tag.id].push(tag.title);
if (tag.tags) {
pathBefore.push(tag.title)
tag.tags.forEach(el => flatMap(el, acc, pathBefore))
}
return acc
}
const keyMap = flatMap({ title: 'Root', id: 'root', tags}, {}, []);
console.log("keyMap", keyMap)
I'm trying to get the path until a tag with no tags and then set that path as value for the ID and then push the items 'own' title. But somehow the paths get messed up.
Check this, makePaths arguments are tags, result object and prefixed titles.
const makePaths = (tags, res = {}, prefix = []) => {
tags.forEach(tag => {
const values = [...prefix, tag.title];
Object.assign(res, { [tag.id]: values });
if (tag.tags) {
makePaths(tag.tags, res, values);
}
});
return res;
};
const tags = [
{
title: "Wood",
id: "dkgkeixn",
tags: [
{
title: "Material",
id: "ewyherer"
},
{
title: "Construction",
id: "cchtfyjf"
}
]
},
{
title: "Steel",
id: "drftgycs",
tags: [
{
title: "Surface",
id: "sfkstewc",
tags: [
{
title: "Polished",
id: "vbraurff"
},
{
title: "Coated",
id: "sdusfgsf"
}
]
},
{
title: "Quality",
id: "zsasyewe"
}
]
}
];
console.log(makePaths(tags));
In my vue-app I have an array of job-postings, which have different states, such as "active", "rejected", "draft", "not_active" etc. Now I have a TabMenu: All Jobs, Drafts and To Be Approved. Each of those MenuTabs, have their own Dropdown menu, where you are supposed to filter the jobpostings. I've realized that this feature is more complex than expected, or maybe I have spend too much time with the issues, but for some reason, I cannot manage, to show "all" for the individual MenuTab. For example, when I click on the "To Be Approved" MenuTab, I want to see all the jobpostings, with the status "Not approved" and "Rejected" (See data below in the code).
So my question is, how to solve this properly? Does the job-posting data object need to have a category too?
Any help is most welcome!
So, here is my component:
<template>
<div>
<div class="tabs">
<ul>
<li v-for="(tab, index) in menuTabs” :key="tab.id" :class="{ 'active': activeTab === index }"
#click="toggleList(tab, index)” >
<span>{{tab.label}}</span>
</li>
</ul>
</div>
<div class="dropdown has-prepend col-8" :class="{ active: isOpen }">
<div :class="{ active: isOpen }" class="dropdown-select" #click="toggle">
{{ selectedOption }}
<i class="icon-chevron_down" />
</div>
<div class="dropdown-options" v-show="isOpen">
<div class="option" v-for="tab in dropDownTabs" #click="select(tab)" :key="tab.id">
<span>{{ tab.status }}</span>
</div>
</div>
</div>
<div class="block">
<DataTable :data="filteredData" :columns="tableColumns" :filter="search" />
</div>
</div>
</template>
import DataTable from '../../snippets/DataTable';
export default {
components: { DataTable },
data() {
return {
isOpen: false,
search: "",
tableData: [
{
id: 1,
title: "Salesperson",
publish_date: "2019-07-10",
status: "active",
applicants: 23,
expiration_date: "2020-02-21"
},
{
id: 2,
title: "Developer",
publish_date: "2019-11-12",
status: "not_active",
applicants: 111,
expiration_date: "2020-02-21"
},
{
id: 3,
title: "Freelanceer",
publish_date: "2019-06-10",
status: "need_approval",
applicants: 222,
expiration_date: "2020-01-10"
},
{
id: 4,
title: "Construction worker",
publish_date: "2019-12-06",
status: "active",
applicants: 76,
expiration_date: "2020-03-15"
},
{
id: 5,
title: "IT support”
publish_date: "2019-11-20",
status: "draft",
applicants: 103,
expiration_date: "2020-04-31"
},
],
menuTabs: [
{
label: "All jobs",
options: [
{
status: "all",
},
{
status: "active",
},
{
status: "not_active"
}
]
},
{
label: "Drafts",
options: [
{
status: "all"
},
{
status: "draft"
}
]
},
{
label: "To Be Approved",
options: [
{
status: "all",
},
{
status: "need_approval",
},
{
status: "rejected"
}
]
},
],
dropDownTabs: [],
selectedOption: "",
selectedTabCategory: "",
category: "",
activeTab: "",
tableColumns: [
"id",
"title",
"publish_date",
"status",
"applicants",
"expiration_date"
]
}
},
computed: {
filteredData() {
let status = this.selectedOption;
let category = this.category;
let filtered = this.tableData.filter(data => {
if (status == "all") {
return data;
}
return data.status === status;
});
return filtered;
}
},
methods: {
toggleList(tab, index) {
this.category = tab.options[0].category;
this.selectedTabCategory = tab;
let currentTabOptions = this.selectedTabCategory.options;
this.clearDropDown();
this.selectedOption = currentTabOptions[0].status;
currentTabOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = index;
},
toggle() {
this.isOpen = !this.isOpen;
},
select(tab) {
this.selectedOption = tab.status;
let category = tab.category;
let filtered = this.tableData.filter(data => {
return data.status === this.selectedOption;
});
this.isOpen = false;
return filtered;
},
clearDropDown() {
this.dropDownTabs = [];
}
},
created() {},
mounted() {
this.selectedOption = this.menuTabs[0].options[0].status;
this.selectedTabCategory = this.menuTabs[0].label;
this.category = this.menuTabs[0].options[0].category;
let defaultOptions = this.menuTabs[0].options;
defaultOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = 0;
}
};
I am not sure if it will help you at all. However I will try anyway.
You should store the selected tab when you click on it. Then filter the this.tableData based on the selected tab options. Also you will need map the tab option options to array of strings, so you can check if the posting status is in there.
methods: {
toggleList (tab, index) {
this.selectedTabObject = tab
// rest of your code...
}
},
computed: {
filteredData () {
return this.tableData.filter(data => {
const states = this.selectedTabObject.options.map(opt => opt.status)
return states.includes(data.status)
})
}
}
Also I have created simple fiddle to mimic your problem.
https://jsfiddle.net/3hqnp7u2/7/
I want to search by header and content. for now can only be based on content. any suggestions or can add from this source code. Thank you
Here is my HTML
<div id="list">
<input type="text" v-model="search">
<ol>
<li v-for="(items, key) in groupedItems">
<h3>{{ key }}</h3>
<p v-for="todo in items">{{ todo.name }}</p>
</li>
</ol>
</div>
here is preview code in js fiddle: https://jsfiddle.net/60jtkp30/9/
It may not be the cleanest solution, but based on your implementation in the jdfiddle, just changing the filter function would be enough (I think).
var list = new Vue({
el: '#list',
data: {
search: '',
items: [
{ name: 'mike', type: 'student' },
{ name: 'beckham john', type: 'footballer' },
{ name: 'walcott', type: 'footballer' },
{ name: 'cech', type: 'footballer' },
{ name: 'jordan', type: 'actor' },
{ name: 'tom', type: 'actor' },
{ name: 'john', type: 'actor' }
]
},
computed: {
groupedItems() {
const arr = {}
//fungsi search
var searchResult = this.items.filter( todo => {
return todo.name.toLowerCase().indexOf(this.search.toLowerCase())>-1 || todo.type.toLowerCase().indexOf(this.search.toLowerCase())>-1;
} )
//grouping
for(var i = 0; i < searchResult.length; i++) {
const key = searchResult[i].type
if (arr[key]) {
arr[key].push(searchResult[i])
} else {
arr[key] = [searchResult[i]]
}
}
return arr
}
}
})