I am using the angular advanced search box and I want to implement a custom filter in my angular page but I am having trouble figuring out how to handle a string of requirements in a query. Let's say I have several objects that follow the following format:
{
"displayName":"John",
"gender":"male",
"type":"customer",
"items": 3,
}
I would want to be able to search in plain english `Anyone who's name is John and is of type Customer". Here is my angular search code so far:
app.filter('infoFilter', function() {
return function(data, query) {
var output = [];
var index;
//loop over the original array
angular.forEach(data, function(row, index) {
angular.forEach(query, function(input, value) {
if(input) {
if(angular.isNumber(row[value]) && row[value] == input) {
output.push(row);
} else if(!angular.isNumber(row[value]) && row[value].toLowerCase().indexOf(input.toLowerCase()) > -1) {
output.push(row);
}
}
});
});
if(query) {
return data;
} else {
return output;
}
}
});
The query comes in as an object that looks like this:
{
"displayName":"John"
}
This works perfectly fine for 1 search parameter. So if I searched for John my table would update to show all entries with the name of john. However, this wouldn't really work for multi search parameters. So if the query looked like this:
{
"displayName":"John",
"gender":"Female"
}
I need to apply all the parameters at once before i do output.push(row). How exactly would I go about doing this?
If I understand you correctly you want to filter the rows where all query parameters apply (AND). I modified your code slightly to achieve this behavior.
app.filter('infoFilter', function() {
return function(data, query) {
var output = [];
var index;
//loop over the original array
angular.forEach(data, function(row, index) {
var pushRow = true;
angular.forEach(query, function(input, value) {
if(input) {
if(angular.isNumber(row[value]) && row[value] == input) {
return;
} else if(!angular.isNumber(row[value]) && row[value].toLowerCase().indexOf(input.toLowerCase()) > -1) {
return;
}
}
pushRow = false;
});
if (pushRow) {
output.push(row);
}
});
// This bit also seems to be the wrong way around in your code.
if(!query) {
return data;
} else {
return output;
}
}
});
Edit:
Here is also an optimized version of the same filter using javascripts built in array functions.
app.filter('infoFilter', function() {
return function(data, query) {
if(!query || !data) {
return data;
}
return data.filter(function(row) {
return Object.keys(query).every(function(key) {
var rowValue = row[key];
var queryValue = query[key];
return (angular.isNumber(rowValue) && rowValue == input) ||
(angular.isString(rowValue) && rowValue.toLowerCase().indexOf(queryValue.toLowerCase()) > -1);
});
});
};
});
Related
I have a file with the following content:
function(doc) {
//pr reqs
var facet = true;
var store = true;
// Template start
var fields = {
}
// template end
noiseList = ["type", "objects", "value"]
const isNumeric = (num) => {
return !isNaN(num)
}
const emitIndex = () => {
if (doc.created_by_ref) {
Object.keys(fields).forEach(function(key) {
if (typeof fields[key] == 'object' && fields[key].length !== undefined) {
if (fields[key].length === 0) {
index(key, 'UNDEFINED', {
'store': store,
'facet': facet
});
} else {
fields[key].forEach(function(ele) {
index(key, ele.toString(), {
'store': store,
'facet': facet
});
})
}
} else {
index(key, fields[key].toString(), {
'store': store,
'facet': facet
});
}
})
}
}
Object.keys(doc).map(obj => {
if (typeof doc[obj] === 'object' && doc[obj] !== null) {
traverseObjectsInDoc(doc[obj], noiseCancelation(obj) ? "" : obj, doc.objects, false);
} else if (doc[obj] !== null && isValueType(obj) && !noiseCancelation(obj)) {
AddToFields(`${obj}`, doc[obj])
}
});
emitIndex();
}
As you I have two special sign there: template start and template end
what I am trying to achieve is to replace sth similar to
var fields = {
"test1": "test",
"test2": "test2"
}
instead of
var fields = {
}
in that file. And I should mention that this fields are generated in runtime so the content needs to be dynamic as well that is why I need this approach. All I can think of is to read the file
const searchAll1 = () => {
const contents = fs
.readFileSync("./lib/design_documents/searchAll", "utf8");
// find the template end and start replace the new fields some and return
};
and find the template end and start replace the new fields somehow and return. However I am not really sure if this is the best way?
What is the best way to do so?
Adjust the design of your function by adding a second parameter you can add the fields object dynamically whenever you call the function.
// your-script.js
module.exports = function(doc, fields) {
...
}
Then when you import and use the function, create a new object and pass it to your function and call it.
const yourFunction = require('./your-script.js');
let doc = someValue;
let fields = {
"test1": "test",
"test2": "test2"
}
yourFunction(doc, fields);
I have had a major headache in recent days creating test scripts to validate more than 300 endpoints of an application we are working on. In the end I came up with a very practical solution that boils down to: a generic JSON validation function and copying and pasting the expected result into an object for testing. This script perform each field validation for a maximum of 3 levels deep inside the JSON.
pm.globals.set("validationHelper", function validationHelper(example) {
for (var field in example) {
if (typeof example[field] === "object") {
pm.test(`Field '${field}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(field);
});
for (var nested in example[field]) {
if (!Array.isArray(example[field][nested])) {
pm.test(`Nested field '${nested}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(nested);
});
} else {
pm.test(`Nested field '${nested}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(nested);
});
for (var index in example[field][nested]) {
if (typeof example[field][nested][index] === "object") {
if (!Array.isArray(example[field][nested][index])) {
for (var child in example[field][nested][index]) {
pm.test(`Child field '${child}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(child);
});
}
}
}
}
}
}
} else {
pm.test(`Field '${field}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(field);
});
}
}
return true
} + ';');
Using Postman, create a Pre-request Script in the collection that you will use to run the tests
Inside the request that you are going to test, paste the following code:
// Save the example response used to validate
// the body using a validation function
example = {
"detail": "Successfully logged out."
}
// This function loads the global helper function
// and starts using the example schema
eval(pm.globals.get("validationHelper"));
validationHelper(example);
The example object you have the save with the response that you are expecting.
Try to send the request and get all green
Postman documentation doesn't recommend to loop tests because of performance, but depending how much time you wanna save, this could be a good solution. :-)
After some research I found a more elegant solution for this same problem. ;-)
pm.globals.set("validationHelper", function validationHelper(example, keys = {}) {
for (var k in example) {
if (typeof example[k] == "object" && example[k] !== null) {
if (k.constructor === String) {
if (!k.match(/^-{0,1}\d+$/)) {
existProperty = false
for (var key in keys) {
(key === k) && (existProperty = true)
}
if (!existProperty) {
keys[k] = true
pm.test(`Child field '${k}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(k);
});
}
}
}
validationHelper(example[k], keys);
} else {
if (k.constructor === String) {
if (!k.match(/^-{0,1}\d+$/)) {
existProperty = false
for (var key in keys) {
(key === k) && (existProperty = true)
}
if (!existProperty) {
keys[k] = true
pm.test(`Child field '${k}' is part of the response`, function () {
pm.expect(pm.response.text()).to.include(k);
});
}
}
}
}
}
return true
} + ';');
Using iTunes' Search API I'm looking to grab a collection of songs from an album based on a specific criteria. I managed to get half of this done with the help of this this solution. The only problem is that there are times where an album can either be clean or explicit, which ultimately outputs duplicate songs.
I thought that the best solution in this case would be to grab the first available (and sometimes only) collection of songs based on their collectionId. Unfortunately this value isn't known offhand like the ones I pass in my filter() function.
How can I cleanly select just one group leveraging the code below:
$(function() {
iTunesSearch("Beauty Behind the Madness", "The Weeknd");
function iTunesSearch(album, artist, callback) {
var results;
$.getJSON("http://itunes.apple.com/search?term=" + album + "&entity=song&callback=?", function(data) {
results = data["results"];
console.log( filter(results, { collectionName: album, artistName: artist }) );
});
}
function filter(arr, criteria) {
return arr.filter(function(obj) {
return Object.keys(criteria).every(function(c) {
return obj[c] == criteria[c];
});
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This filter function does what you asked, i.e. it grabs the first collection of tracks
function filter(arr, criteria) {
var tracks = arr.filter(function(track) {
return Object.keys(criteria).every(function(c) {
return track[c] == criteria[c];
});
});
if (!tracks.length) {
return [];
}
return tracks.filter(function(track) {
return track.collectionId == tracks[0].collectionId;
});
}
Edit:
Here is an optimization to avoid having two filter passes, but it makes it a bit harder to read:
function filter(arr, criteria) {
var firstCollectionId = null;
return arr.filter(function(track) {
var isMatch = Object.keys(criteria).every(function(c) {
return track[c] == criteria[c];
});
if (isMatch) {
if (firstCollectionId === null) {
firstCollectionId = track.collectionId;
return true;
}
return track.collectionId === firstCollectionId;
}
return false;
});
}
Here's a way to solve it :)
$(function() {
iTunesSearch("Beauty Behind the Madness", "The Weeknd");
function iTunesSearch(album, artist, callback) {
var results;
$.getJSON("http://itunes.apple.com/search?term=" + album + "&entity=song&callback=?", function(data) {
results = data["results"];
console.log( filter(results, { collectionName: album, artistName: artist }, 'collectionId') );
});
}
function filter(arr, criteria, key) {
var keys = [];
return arr.filter(function(obj) {
var keyVal = obj[key];
var result = Object.keys(criteria).every(function(c) {
return obj[c] == criteria[c];
}) && !keys.includes(keyVal);
if(keyVal) keys.push(keyVal);
return result;
});
}
});
I believe I understand what it is you need... to have an array of unique songs from a particular album. If this is correct, here's a solution:
$(function() {
iTunesSearch("Beauty+Behind+the+Madness", "The+Weeknd");
function iTunesSearch(album, artist, callback) {
var results,
tracks = [];
$.getJSON("http://itunes.apple.com/search?term=" + album + "&media=music&entity=song&callback=?", function(data) {
data.results.map(function (album) {
if (tracks.indexOf(album.trackName) === -1) {
tracks.push(album.trackName);
}
});
console.log(tracks);
});
}
});
http://jsfiddle.net/4vj05v7c/
I opted to populate the array with only track names so it would be easier to see that duplicates were not included.
I have a table with some data. It has name, code, phone, etc.. And this table can have dynamic fields, based on the client option.
I could found a code to make this search ignore the punction, but it has some limitations. On the .filter i need to specify which field I'm going to search, so, because my table is dynamic i don't know what field is being displayed.
This is the link i got the answer from.
And this is the code I'm using:
app.js
.filter('filterMaster', function() {
return function(items, searchTerm) {
if (!searchTerm || '' === searchTerm) {
return items;
}
searchTerm = searchTerm.replace(/[^\w\s]|_/g, "").toLowerCase();
return items.filter(function(element, index, array) {
var title = element.cod_order.replace(/[^\w\s]|_/g, "").toLowerCase();
return title.indexOf(searchTerm) > -1;
});
}
})
I also tried using this code, which i got from this answer:
app.js
$scope.ignoreAccents = function(item) {
if (!$scope.searchField)
return true;
var text = removeAccents(item.cod_order.toLowerCase())
var search = removeAccents($scope.searchField.toLowerCase());
return text.indexOf(search) > -1;
};
function removeAccents(value) {
return value
.replace(/á/g, 'a')
.replace(/é/g, 'e')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/[^\w\s]|_/g, "")
.replace(/ú/g, 'u');
};
But again, if i don't specify the cod_order, or other field I want to search, it doesn't work. And if i set the field i want to search, than i can't search in anyother field on the table.
The main problem with this, is because i can't search any other field if it's not being set inside this filter.
Is there a way to optimize it so it work doesn't matter what field the table has? And without the need to specify the field name?
Try something like this
.filter('filterMaster', function () {
return function (items, searchTerm) {
if (searchTerm === '') return items;
var filtered = [];
var str = searchTerm.replace(/\./g, '');
for (var i = 0; i < items.length; i++) {
var itemTmp = items[i];
var found = false;
$.each(itemTmp, function (i, n) {
if(i =='$$hashKey' || found )
return;
var replaced = n.toString().replace(/\./g, '');
if (replaced.indexOf(str) >= 0)
{
filtered.push(itemTmp);
found = true;
}
});
}
return filtered;
};
});
here working code.
I have a simple form which users can enter a "tweet". I ahve some javascript behind the scenes to control what happens when a url is entered.
If a url is entered such as test.com then a new input field will appear.
If a url that is stored in an array is entered, it will and the new input field along with a select option.
here is my javascript:
var test = ["test1.com", "test2.com", "test3.com"];
$('#tweet_text_ga').hide();
$('#custom_alias').hide();
$('#tweet_campaign').hide();
$('#tweet_text').keydown(function () {
var val = this.value;
if (/\.[a-zA-Z]{2,3}/ig.test(val)) {
$('#custom_alias').show();
} else {
$('#custom_alias').hide();
}
if ($.inArray(val, test) !== -1) {
$('#tweet_campaign').show();
} else {
$('#tweet_campaign').hide();
}
});
It works fine if just a url is entered. But as soon as you add more text, it disregards if the url is in the array, and removes the select option. I'm not quite sure on how to explain this any better, so i have setup a fiddle to show what i mean.
I hope someone understands and can point me in the right direction
Fiddle
That's because you are checking if a whole input is in the array: if ($.inArray(val, test) !== -1). You need to retrieve URL from the input using a regex and check that.
Write a regex that retrieves any URL, get that URL and check if it's one of your lucky ones:
var urlsInInput = /[a-z0-9]+\.[a-zA-Z]{2,3}/ig.exec(val);
if (urlsInInput.length == 1 && $.inArray(urlsInInput[0], test) !== -1) {
instead of
if ($.inArray(val, test) !== -1) {
Fiddle
Here is my version handling the first url
Live Demo
$('#tweet_text').keydown(function () {
var val = this.value;
var urls = val.match(/[a-z0-9]+\.[a-zA-Z]{2,}/ig);
var alias = (urls && urls.length>0)
$('#custom_alias').toggle(alias);
var tweet = urls && urls.length>0 && $.inArray(urls[0], test) !== -1;
$('#tweet_campaign').toggle(tweet);
});
What #siledh said. Here is how you could use your current test array
var reg = new RexExp(test.join('|').replace(/\./ig, "\\."), 'ig')
if( reg.test(val) ) {
$('#tweet_campaign').show();
} else {
$('#tweet_campaign').hide();
}
The reason the campaign field begins to disappear again is that you compare the whole value of the input with the array. If you just find all domain matches and then compare them to your array it should work.
Like so:
var test = ["test1.com", "test2.com", "test3.com"];
$('#tweet_text_ga').hide();
$('#custom_alias').hide();
$('#tweet_campaign').hide();
$('#tweet_text').keyup(function () {
var alias = false;
var campaign = false;
var domain = /([a-z0-9]+(:?[\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})/ig;
var val = this.value;
var match = val.match(domain);
if (match) {
alias = true;
match.forEach(function(e) {
campaign = campaign || ($.inArray(e, test) !== -1);
});
}
if (alias === true) {
$('#custom_alias').show();
} else {
$('#custom_alias').hide();
}
if (campaign === true) {
$('#tweet_campaign').show();
} else {
$('#tweet_campaign').hide();
}
});
Something wrong with your $.isArray(val, test), the value you use is the whole value.
And not sure your purpose, so write a code like this. hope it would help.
http://jsfiddle.net/sheldon_w/KLuK8/
var test = ["test1.com", "test2.com", "test3.com"];
var urlReg = /[^\s]+\.[a-zA-Z]{2,3}/ig;
$('#tweet_text_ga').hide();
$('#custom_alias').hide();
$('#tweet_campaign').hide();
$('#tweet_text').keydown(function () {
var val = this.value;
var matchedUrls = [];
val.replace(urlReg, function (matched) {
matchedUrls.push(matched);
});
if (matchedUrls.length > 0) {
$('#custom_alias').show();
} else {
$('#custom_alias').hide();
}
$(matchedUrls).each(function (idx, url) {
if ($.inArray(url, test) !== -1) {
$('#tweet_campaign').show();
return false;
} else {
$('#tweet_campaign').hide();
}
});
});