I have used the AngularJS filter to search objects and even used the deep search with the $, however partial searches like the below never return the result that I would expect, I can split the search input into an array using .split(" ") though I do not know how to correctly pass this array to the filter.
I have tried to define my own filter with no success.
Data:
[
{
Description: "test Description",
Color: "Blue",
Size: "Large"
}, {
Description: "aest 2",
Color: "orange",
Size: "Large"
}
, {
Description: "test 3",
Color: "black",
Size: "small"
}
]
Search input -> expected behavior
"tes blu" -> return one result
"te desc" -> return one result
"purple" -> return zero results
Any advice on how to proceed would be greatly appreciated.
I am using AngularJS: v1.3.13
code here: http://plnkr.co/edit/j3NqCtO1qHJrMuj6y2nv?p=preview
this is how I solved it: http://plnkr.co/edit/W66NxRlh8deRUh6Uxpor?p=preview
var App = angular.module("App",[]);
App.controller("testCtrl",function($rootScope,$scope){
$scope.testObjects = [
{
Description: "test Description",
Color: "Blue",
Size: "Large"
}, {
Description: "aest 2",
Color: "orange",
Size: "Large"
}
, {
Description: "test 3",
Color: "black",
Size: "small"
}
];
$scope.Search = "";
$rootScope.ScoreThings = function(object , inputArray){
var result = 0;
for (var x = 0; x < inputArray.length; x++)
{
angular.forEach(object, function(value, key) {
if (value.toLowerCase().indexOf(inputArray[x].toLowerCase()) > -1)
{
result ++;
}
});
}
return result;
}
});
App.filter('SearchFilter2', function ($rootScope) {
return function (items, inputString) {
var filtered = [];
var inputArray = inputString.split(" ");
for (var i = 0; i < items.length; i++)
{
if ($rootScope.ScoreThings(items[i], inputArray) > (inputArray.length / 1.2) )
{
filtered.push(items[i]);
}
}
return filtered;
};
});
use it in your view like:
<tr ng-repeat="testObject in testObjects | SearchFilter2 : Search ">
Related
I have this JSON tree view that represents a menu :
var menus = [
{
label : "1",
items: [
{
label : "1.1"
},
{
label : "1.2",
items : [
{
label : "1.2.1"
},
{
label : "1.2.2"
}
]
},
{
label : "1.3"
},
]
},
{
label : "2"
}
]
I want to mutate this JSON by adding for each item a selected property. This property, a boolean, will be set to true if the label is the right one or this is the tricky part if the descendant is the right one.
For instance, if I'm looking for label 1.2, all labels 1 and 1.2 will be selected. So I will get this JSON :
var menus = [
{
label : "1",
selected : true,
items: [
{
label : "1.1"
selected : false
},
{
label : "1.2",
selected : true,
items : [
{
label : "1.2.1"
selected : false
},
{
label : "1.2.2",
selected : false
}
]
},
{
label : "1.3",
selected : false
},
]
},
{
label : "2",
selected : false
}
]
the selected : false is not necessary.
Lodash is OK for me;)!
Any suggestions?
edit : where I am ! --> https://codepen.io/anon/pen/XGoXjM?editors=0010
edit 2 : finding elements must not be based on the way I wrote the labels. The labels can be any string... Sorry...
Thanks
This solution uses a for loop to iterate recursively the menu items and their children. If an item is selected, it adds selected: true to the item and it's parents:
const selectMenuItems = menus => selectedLabel => {
const internal = arr => {
let selected = false
for(let i = 0; i < arr.length; i++) {
const item = arr[i]
const childrenSelected = !!item.items && internal(item.items)
item.selected = childrenSelected || item.label === selectedLabel
selected = selected || item.selected
}
return selected
}
internal(menus)
return menus
}
const menus = [{"label":"1","items":[{"label":"1.1"},{"label":"1.2","items":[{"label":"1.2.1"},{"label":"1.2.2"}]},{"label":"1.3"}]},{"label":"2"}]
const menuSelector = selectMenuItems(menus)
const result = menuSelector('1.2')
console.log(result)
.as-console-wrapper { top: 0; max-height: 100% !important; }
I would simply check labels this way:
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
var checkSelected = function(items, search) {
for (var key in items) {
items[key].selected = search.startsWith(items[key].label) && items[key].label.length<=search.length;
if (items[key].items) {
checkSelected(items[key].items, search);
};
};
};
var test = "1.2";
checkSelected(menus, test);
console.log(menus);
Also on JSFiddle.
The startsWith() method determines whether a string begins with the
characters of a specified string, returning true or false as
appropriate.
quoted from here
You can use some recursive approach to implement this.
let str = '1.2.1';
function checkItem(arr, strArr) {
// iterate over the array
arr.forEach((obj) => {
// set selected property based on matching every digit in label in same order
// if digits would be single then you can use startsWith and no need to split string
obj.selected = obj.label.split('.').every((it, i) => it === strArr[i]);
// if nested item is there then call recursively
obj.items && checkItem(obj.items, strArr);
});
return arr;
}
checkItem(menus, str.split('.'));
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
let str = '1.2.1';
function checkItem(arr, strArr) {
arr.forEach((obj) => {
obj.selected = obj.label.split('.').every((it, i) => it === strArr[i]);
obj.items && checkItem(obj.items, strArr);
});
return arr;
}
checkItem(menus, str.split('.'));
console.log(menus);
UPADATE : Since you want to update selected property completely independent of label you can do something like follows. I assume you want to
update based on position in the array.
let str = '1.2.1';
function checkItem(arr, prefixArray, strArr) {
// iterate over the array
arr.forEach((obj, i) => {
// generate new prefix array for checking
let pa = [...prefixArray, i + 1];
// compare prefix array with the string array to check matches
obj.selected = pa.every((it, i) => it == strArr[i]);
// if items defined do it recursively
obj.items && checkItem(obj.items, pa, strArr);
});
return arr;
}
checkItem(menus,[], str.split('.'));
var menus = [{
label: "1",
items: [{
label: "1.1"
},
{
label: "1.2",
items: [{
label: "1.2.1"
},
{
label: "1.2.2"
}
]
},
{
label: "1.3"
},
]
},
{
label: "2"
}
];
let str = '1.2.1';
function checkItem(arr, prefixArray, strArr) {
arr.forEach((obj, i) => {
let pa = [...prefixArray, i + 1];
obj.selected = pa.every((it, i) => it == strArr[i]);
obj.items && checkItem(obj.items, pa, strArr);
});
return arr;
}
checkItem(menus,[], str.split('.'));
console.log(menus);
I've taken the following sample from a different question. And I am able to identify the object. But I also need to find our the position of that object. For example:
var arr = [{
Id: 1,
Categories: [{
Id: 1
},
{
Id: 2
},
]
},
{
Id: 2,
Categories: [{
Id: 100
},
{
Id: 200
},
]
}
]
If I want to find the object by the Id of the Categories, I can use the following:
var matches = [];
var needle = 100; // what to look for
arr.forEach(function(e) {
matches = matches.concat(e.Categories.filter(function(c) {
return (c.Id === needle);
}));
});
However, I also need to know the position of the object in the array. For example, if we are looking for object with Id = 100, then the above code will find the object, but how do I find that it's the second object in the main array, and the first object in the Categories array?
Thanks!
Well, if every object is unique (only in one of the categories), you can simply iterate over everything.
var arr = [{
Id: 1,
Categories: [{Id: 1},{Id: 2}]
},
{
Id: 2,
Categories: [{Id: 100},{Id: 200}]
}
];
var needle = 100;
var i = 0;
var j = 0;
arr.forEach(function(c) {
c.Categories.forEach(function(e) {
if(e.Id === needle) {
console.log("Entry is in position " + i + " of the categories and in position " + j + " in its category.");
}
j++;
});
j = 0;
i++;
});
function findInArray(needle /*object*/, haystack /*array of object*/){
let out = [];
for(let i = 0; i < haystack.lenght; i++) {
if(haystack[i].property == needle.property) {
out = {pos: i, obj: haystack[i]};
}
}
return out;
}
if you need the position and have to filter over an property of the object you can use a simple for loop. in this sample your result is an array of new object because there can be more mathches than 1 on the value of the property.
i hope it helps
Iterate over the array and set index in object where match found
var categoryGroups = [{
Id : 1,
Categories : [{
Id : 1
}, {
Id : 2
},
]
}, {
Id : 2,
Categories : [{
Id : 100
}, {
Id : 200
},
]
}
]
var filterVal = [];
var needle = 100;
for (var i = 0; i < categoryGroups.length; i++) {
var subCategory = categoryGroups[i]['Categories'];
for (var j = 0; j < subCategory.length; j++) {
if (subCategory[j]['Id'] == findId) {
filterVal.push({
catIndex : i,
subCatIndex : j,
id : needle
});
}
}
}
console.log(filterVal);
Here is solution using reduce:
var arr = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }, ] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }, ] } ]
const findPositions = (id) => arr.reduce((r,c,i) => {
let indx = c.Categories.findIndex(({Id}) => Id == id)
return indx >=0 ? {mainIndex: i, categoryIndex: indx} : r
}, {})
console.log(findPositions(100)) // {mainIndex: 1, categoryIndex: 0}
console.log(findPositions(1)) // {mainIndex: 0, categoryIndex: 0}
console.log(findPositions(200)) // {mainIndex: 1, categoryIndex: 1}
console.log(findPositions(0)) // {}
Beside the given answers with fixt depth searh, you could take an recursive approach by checking the Categories property for nested structures.
function getPath(array, target) {
var path;
array.some(({ Id, Categories = [] }) => {
var temp;
if (Id === target) {
path = [Id];
return true;
}
temp = getPath(Categories, target);
if (temp) {
path = [Id, ...temp];
return true;
}
});
return path;
}
var array = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 },] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];
console.log(getPath(array, 100));
.as-console-wrapper { max-height: 100% !important; top: 0; }
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
Here field color is of type String, and Location is an array.
I have persons, and then there is a filter criteria. I need an output so that all the values selected in the filter needs to match with the data. So in the data provided, only those records should be visible if Silver, Up and Down are available in a record. (Note the AND parameter, there is no OR condition anywhere).
So the output will be:
{ Color: "Silver", Location: ["Up", "Down"] }
Now if the filter criteria is:
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up"] }
];
the output will be:
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
So you see all the values in the filter should match with the records.
I broke down the problem into separate functions. It's way more verbose than your solution but I do think it's more readable.
Also: it does work.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
const arraysEqual = (arr1, arr2) => {
// Very simple array comparison.
if (arr1.length !== arr2.length) return false;
arr1 = arr1.sort();
arr2 = arr2.sort();
for(let i=0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
};
let result = persons.filter(person => {
// All criteria must match.
for (let criterium of criteria) {
if (criterium.Field === 'Color') {
if (person.Color !== criterium.Values[0]) return false;
}
if (criterium.Field === 'Location') {
if (!arraysEqual(person.Location, criterium.Values)) return false;
}
}
// We didn't *not* match for anything, so we matched!
return true;
});
console.log(result);
/*
{ Color: "Silver", Location: ["Down", "Up"] }
*/
See https://repl.it/#niels_bom/GoldenIdealMicrobsd for a running example.
After third edit, now I am clear on what you want - to have an array as a subset of the other.
var result = persons.filter(function (person) {
return criteria.every(function (c) {
var value = person[c.Field];
if (typeof value === 'object') {
return c.Values.length<=value.length &&
c.Values.every(function(v,i){return value.includes(v) ;});
}
else
return c.Values.indexOf(value) > -1;
})
})
I also updated your jsfiddle : https://jsfiddle.net/gzL42dna/1/
Also checkout: How to compare arrays in JavaScript?
I believe this works. Using a Set helps a lot with partial matches. Tell me if you would like an explanation of the code.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
console.log(match(persons, criteria));
function match(persons, criteria) {
let personMatches = [...persons]
for (let i=0; i < criteria.length; i++) {
let {Field, Values} = criteria[i]
personMatches = personMatches.filter(obj => {
if (Array.isArray(obj[Field])) {
return hasMatches(obj[Field], Values)
} else {
return Values.includes(obj[Field])
}
})
}
return personMatches
}
function hasMatches(arr1, criteria) {
let criteriaSet = new Set(criteria)
let personsSet = new Set(arr1)
for (let el of criteriaSet) {
if (!personsSet.has(el)) return false
}
return true
}
I found this solution for the kind of problem I'm trying to solve here
The only difference is that my array of object has more than 2 elements
and the result wanted is similar to the solution but with all the elements
{
"group": "one",
"color": ["red", "green", "black"],
"size": ["big"],
"date": ["11/08/2018"]
}
So I've been repeating the .map() to get all my values to show but I feel that I shouldn't ...
Can someone please help me with and simpler and better option?
var db = [{"Record":{"id":"26","cost_center":"15073 DC1 M8 - Filmatic","batch_no":"367746","item_code":"12583","description":"LF Fruited Guava (2x6)x15"}},{"Record":{"id":"29","cost_center":"15073 DC1 M8 - Filmatic","batch_no":"367749","item_code":"12583","description":"LF Fruited Guava (2x6)x15"}},{"Record":{"id":"36","cost_center":"15093 DC1 M10 - CornerPot Machi","batch_no":"367756","item_code":"12256","description":"PROMO CP LF SaltedCar w H"}}];
var myArray = [];
for (var i in db) {
if (db.hasOwnProperty(i)) {
myArray.push(db[i].Record);
}
}
var res = myArray.reduce(function(res, elem) {
if (res.indexOf(elem.cost_center) === -1) {
res.push(elem.cost_center);
}
return res;
}, []).map(function(machine) {
return {
cost_center: machine,
batch_no: myArray.filter(function(_el) {
return _el.cost_center === machine;
}).map(function(_el) { return _el.batch_no; }),
item_code: myArray.filter(function(_el) {
return _el.cost_center === machine;
}).map(function(_el) { return _el.item_code; }),
description: myArray.filter(function(_el) {
return _el.cost_center === machine;
}).map(function(_el) { return _el.description; })
}
});
console.log(res);
For the later added code, you could use a hash table, where you collect all objects with the same cost_center and use another array for collecting the values of the properties, like batch_no, item_code and description.
var db = [{ Record: { id: "26", cost_center: "15073 DC1 M8 - Filmatic", batch_no: "367746", item_code: "12583", description: "LF Fruited Guava (2x6)x15" } }, { Record: { id: "29", cost_center: "15073 DC1 M8 - Filmatic", batch_no: "367749", item_code: "12583", description: "LF Fruited Guava (2x6)x15" } }, { Record: { id: "36", cost_center: "15093 DC1 M10 - CornerPot Machi", batch_no: "367756", item_code: "12256", description: "PROMO CP LF SaltedCar w H" } }],
keys = ["batch_no", "item_code", "description"],
hash = Object.create(null),
result = [];
db.forEach(function (o) {
if (!hash[o.Record.cost_center]) {
hash[o.Record.cost_center] = { cost_center: o.Record.cost_center };
keys.forEach(function (k) {
hash[o.Record.cost_center][k] = [o.Record[k]];
});
result.push(hash[o.Record.cost_center]);
return;
}
keys.forEach(function (k) {
hash[o.Record.cost_center][k].push(o.Record[k]);
});
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var set = [{"color":"blue"},{"color":"green"},{"color":"red"},{"color":"green"}];
I'd like to be able to do something like a db call, set.find({"color":"green"}) and have it return an array full of objects that contain that property.
Using Array#filter, for this particular case the code would look like
var results = set.filter(function (entry) { return entry.color === "green"; });
Array#filter is not implemented in some older browsers, so see the linked article for a backward compatibility shim, or better yet get a full-fledged ES5 shim.
For the more general case, it's just a matter of extending this idea:
function findByMatchingProperties(set, properties) {
return set.filter(function (entry) {
return Object.keys(properties).every(function (key) {
return entry[key] === properties[key];
});
});
}
var results = findByMatchingProperties(set, { color: "green" });
Again, I am using ECMAScript 5 methods Object.keys and Array#every, so use an ES5 shim. (The code is doable without an ES5 shim but uses manual loops and is much less fun to write and read.)
I have used map function from jquery and I am getting selected index by passing searched key value so by using that index we will get required object from array.
var mydata = [{ name: "Ram", Id: 1 }, { name: "Shyam", Id: 2 }, { name: "Akhil", Id: 3 }];
searchKey = 2
var mydata = [{ name: "Ram", Id: 1 }, { name: "Shyam", Id: 2 }, { name: "Akhil", Id: 3 }];
searchKey = 2
var selectedData = mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey)];
console.log(selectedData)
var selectedData = mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey)];
console.log(selectedData)
output
{ name: "Shyam", Id: 2 }
Note: if you want to pass search key as object then
searchKey = { Id: 2 };
mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey.Id)];
output
{ name: "Shyam", Id: 2 }
Using arrow functions with an implied return and concise body:
const results = set.filter(entry => entry.color === "green");
Another example passing in a search variable:
const searchString = 'green';
const results = set.filter(entry => entry.color === `${searchString}`);
Read more about arrow functions on
MDN
Since you've included the jQuery tag, here's one way to do it using jQuery's map:
var results = $.map( set, function(e,i){
if( e.color === 'green' ) return e;
});
The documentation states that you need to return null to remove the element from the array, but apparently this is false, as shown by the jsFiddle in the comments; returning nothing (i.e. returning undefined) works just as well.
I went with a different approach that I found to be a bit easier.
function isObjEqual(a, b) {
const x = JSON.stringify(a);
const y = JSON.stringify(b);
return x === y;
}
// Example 1
const set = [{"color":"blue"},{"color":"green"},{"color":"red"},{"color":"green"}];
const findObj1 = {"color":"green"};
const arr1 = set.filter((objInArr) => isObjEqual(objInArr, findObj1));
console.log(arr1) // [ { color: 'green' }, { color: 'green' } ]
// Example 2
const list = [{
"label": "Option 2",
"value": "option2"
},
{
"label": "Option 3",
"value": "option3"
},
{
"label": "Option 2",
"value": "option2"
}
];
const findObj2 = {
"label": "Option 2",
"value": "option2"
}
const newList = list.filter((objInArr) => isObjEqual(objInArr, findObj2));
console.log(newList) //[ { label: 'Option 2', value: 'option2' }, { label: 'Option 2', value: 'option2' } ]