How to improve my backbone.js search Function? - javascript

I have a search function as part of a backbone app that I'm writing. I'd like some help expanding it out. Ideally I'd like it to be able to
search more than one category in my model and
not have to be such an exact match (perhaps using RegEx?)
Here's my code:
search: function(e) {
var search = this.$('.search').val(); //pulling in from the search bar
results = namesCollection.filter(function(item) { return item.get('First_Name') == (search);}); //returns a filtered collection that matches (exactly) the search results
filteredCollection.reset(results); //this just resets the current view
}
The code works, but right now it only returns results for an exact match on First_Name. Is there any way to have it match both First_Name and Last_Name? Also I'm trying to find a way to have it return for incomplete searches. So that entering 'Joh' in the search bar, would return a First_Name of John and Johanna.
Here's what my data looks like (simplified, of course):
var data = [
{
"First_Name": "John",
"Last_Name": Smith
},
{
"First_Name": "Johanna",
"Last_Name": "Greene"
}
];
And namesCollection refers to a backbone collection of all the names in my data set. Thanks!

As for the searching first and last names why not just run another filter on your filtered results? You could split the search by spaces.
results = namesCollection.filter(function(item) { return item.get('First_Name') == (search.split(' ')[0]);});
if(search.split(' ').length > 1){
results = namesCollection.filter(function(item) { return item.get('Last_Name') == (search.split(' ')[1]);});
}
As for finding a similar string that might work with mispelled words you are probably going to need something like a Levenshtein function which allows you to calculate the similarity between to strings. You could then determine based on your own threshold whether or not to consider it a match. Here is a js library for it
If you want to do partial matches as well assuming they are correctly spelled you could first search for exact matches and if one is found use it (let's say for the first name which means there is only one element after split). If an exact match is not found then try a partial match with an indexOf(). If you get a match on the first name and there is also a last name (meaning there are more than one elements in the split) then attempt an exact match of the last name. If that last name match fails then do the partial match search.

I would leverage lodash/underscore. Makes life easy. You can probably deconstruct to native js, but I find this easier.
#!/usr/bin/env node
_ = require('lodash');
var search = 'john';
var data = [
{
"First_Name": "John",
"Last_Name": "Smith"
},
{
"First_Name": "Jessica",
"Last_Name": "Greene"
},
{
"First_Name": "Sam",
"Last_Name": "Johnson"
}
];
var re = new RegExp(search, 'i');
data = data.filter(function(model){
var matched = false;
_.each(_.values(model), function(val){
if ( re.test(val) ) {
matched = true;
}
});
return matched;
});
console.log(data);
Outputs:
[ { First_Name: 'John', Last_Name: 'Smith' },
{ First_Name: 'Sam', Last_Name: 'Johnson' } ]
To work off a backbone collection you need to do this.collection.filter and also use model.attributes for the _.values(model.attributes, cb). assuming its a flat object.
This would of course match any value in the object, and I did it in node, but would easily run in backbone/browser.

I am not too familiar with Backbone, but what about changing the exact matching to a .indexOf or .contains? That way it can filter while you type in the keyword and so on keyup you can move to a more exact matching, the more keywords you give..
This is for instance the system that shuffle.js uses. Have a look at how that search works, might be what you are searching for i think.
search: function(e) {
var search = this.$('.search').val(); //pulling in from the search bar
results = namesCollection.filter(function(item) { return item.get('First_Name').indexOf( search ) !== -1 }); //returns a filtered collection that matches (not exactly) the search results
filteredCollection.reset(results); //this just resets the current view
}

Related

Possible to use placeholders in sys_properties?

Is it possible to create a sys_property value such as:
This incident has been picked up by {person_name} for further investigation
And then in the code use something like:
var assignee = "John Doe";
var prop = gs.getProperty('my_property_name');
// and now replace person_name with the value of assignee variable?
You can use the RecordToHTML API, traditionally it's used for using the fields from a record as the replacement values for the string fields, however, with the help of .setValue(), you can bypass the record arguments and specify your own values. Note for this to work your "variables" in your string need to be wrapped in ${} and not {}:
var prop = gs.getProperty('my_property_name'); // "This incident has been picked up by ${person_name} for further investigation";
var assignee = "John Doe";
var rth = new RecordToHTML(null, null, prop);
rth.setValue("person_name", assignee); // replaces ${person_name} with "John Doe"
var result = rth.toString(); // "This incident has been picked up by John Doe for further investigation"
The above is a little hacky, as it bypasses the record arguments. If you want to go for another appraoch you can create your own function for this. You can firstly put assignee into an object that holds the key person_name and points to your assignee value, then use a function to replace values within {<key>} using the key as the key to index your object:
function interpolate(str, obj) {
return str.replace(/{([^}]+)}/g, function(match, key) {
return obj[key] || match; // if we can't find the key, return the original `{key}`
});
}
var variableMap = {
"person_name": "John Doe" // assignee
};
var prop = gs.getProperty('my_property_name');
var msg = interpolate(prop, variableMap); // "This incident has been picked up by John Doe for further investigation"
There are some other options that you can use that may do what you're after that are also worth looking into. One being using the first arguments of RecordToHTML() and another being gs.getMessage(). RecordToHTML can also be good if you have a particular record that contains the fields you want to replace from your string (note that your fields need to be wrapped in ${}):
var rth = new RecordToHTML("incident", incidentSysId, "This incident has been picked up by ${assigned_to} for further investigation", false);
var result = rth.toString();
There is also gs.getMessage(), which comes close, but it doesn't allow for named "variables" within your string and requires you to use indexes. It also performs a table query / lookup in the sys_ui_message table each time it is called which may be overkill:
// prop = This incident has been picked up by {0} for further investigation
var msg = gs.getMessage(prop, [assignee]);

Efficient way to search within multiple values in json

I have multiple records like this,
name: John Doe aliases: John, Doe, JD unique_id: 1 ...
My question is how do I search efficiently within the aliases & full name.
If the search query is any of those 4 (John Doe, John, Doe, JD) I would like to find the unique id (in this case 1).
What I have done: I have a very straightforward implementation that loops through the entire data until it finds. It takes a long time since the number of fields is very high.
Note: I am using javascript if it helps. Also I have the permission to change the data format (permanently), if it will make the search more efficient. Most of the search queries tend to be one of the aliases rather than full name.
Sample Code: https://jsfiddle.net/nh7yqafh/
function SearchJSON(json, query) {
var champs = json.champs;
for (var i = 0; i < champs.length; ++i) {
if (query == champs[i].name)
return champs[i].unique_id;
for (var j = 0; j < champs[i].aliases.length; ++j) {
if (query == champs[i].aliases[j])
return champs[i].unique_id;
}
}
}
//Data format is similar to what vivick said
var json_string = '{"count":5,"champs":[{"name":"Abomination","aliases":["abomination","AB","ABO"],"unique_id":1},{"name":"Black Bolt","aliases":["blackbolt","BB","BBT"],"unique_id":2},{"name":"Black Panther","aliases":["blackpanther","BP","BPR"],"unique_id":3},{"name":"Captain America","aliases":["captainamerica","CA","CAP"],"unique_id":4}]}'
var json = JSON.parse(json_string);
query="CA";
alert( "id of "+query+" is "+SearchJSON(json, query));
I guess you have a structure similar to the following one :
[
{
"name": "xxx",
"aliases": ["x", "xx", "xxx"],
"unique_id": 1,
/* [...] */
},
/* [...] */
]
You can then do something like this :
const queryParam = /*search query*/;
const arr = /* get the JSON record */;
const IDs = arr
.filter( entry =>(entry.aliases.includes(queryParam) || queryParam===entry.name) )
.map(entry=>entry.uniqueId);
This will give you an array of IDs which are potential matches.
If you need either 0 or 1 result only :
const ID = IDs[0] || null;
This will simply retrieve the first matched ID if there's one, otherwise it will set ID to null.
NB:
If you use an object of objects instead of an array of object, there's just a little bit of modifications to do (mainly using Object.entries) but it still is trivial.
PS:
I would recommend to always add the full name in the aliases, this will ease the filtering part (no || would be required).

Validating list of email extention set as JSON in JavaScript

How can I validate user inputted email and check their extension with a list of email extensions in a JSON?
Like if I type abc#efg.com its going to check only the email extension which is #efg.com in a list of JSON.
OR a regex that will only get the values after "#" and ignore anything before that.
[
{
"School": "Ivy Tech Community College",
"Email": "ivytech.edu"
},
{
"School": "Liberty University",
"Email": "liberty.edu"
},
{
"School": "Miami Dade College",
"Email": "mdc.edu"
},
{
"School": "Lone Star College",
"Email": "lonestar.edu"
},
{
"School": "Ashford University",
"Email": "ashford.edu"
}
]
// initial data
var data = '[ {"School":"Ivy Tech Community College","Email":"ivytech.edu"},' + '{"School":"Liberty University","Email":"liberty.edu"},' + '{"School":"Miami Dade College","Email":"mdc.edu"},' + '{"School":"Lone Star College","Email":"lonestar.edu"},' + '{"School":"Ashford University","Email":"ashford.edu"} ]';
// json-ify our data
var jsonData = JSON.parse(data);
// map the values of each JSON 'Email' property from jsonData in an array
var emailsArray = jsonData.map(function (x) { return x.Email; });
// email address for testing
var testEmail = "john#liberty.edu";
// split the email address by the "#" character and use the second part (domain)
if (arrayContains(testEmail.split("#")[1], emailsArray))
{
// this will fire as john#liberty.edu matches liberty.edu in emailsArray
console.log("emailsArray contains domain");
}
else
{
console.log("emailsArray does not contain domain");
}
// function to check if an item is contained in an array
function arrayContains(item, array)
{
return (array.indexOf(item) > -1);
}
Complete JSFiddle example here.
Notes:
you can ignore the first two lines of code as I'm guessing you're getting your JSON data from a web response
an assumption is being made that testEmail adheres to the format of an email address; you might need to implement some kind of validation to verify that the string being input is an actual email
we split testEmail by the # character and get the second part of the result (which will be at index 1, since arrays are zero-based) using String.prototype.split()
the emailsArray array is created using the Array.prototype.map() function
arrayContains uses the String.prototype.indexOf() method to check if testEmail exists in emailsArray
I think I've clarified what every line of code in the example does. You can now take it and adjust it to your own requirements—and even better, improve it.
Instead of regex, you can iterate through the array of schools, and match the domain like this:
var schools = [
{"School":"Ivy Tech Community College","Email":"ivytech.edu"},
{"School":"Liberty University","Email":"liberty.edu"},
{"School":"Miami Dade College","Email":"mdc.edu"},
{"School":"Lone Star College","Email":"lonestar.edu"},
{"School":"Ashford University","Email":"ashford.edu"}
]
function validate(email) {
var domain = email.split('#').pop();
for(var i = 0; i < schools.length; i++) {
if(domain === schools[i].Email) return schools[i].School;
}
return 'Domain Not Found';
}
You can replace schools[i].School with true and 'Domain Not Found' with false to just check with it exists.
Example: https://jsfiddle.net/TheQueue841/gwhkq520/
Something like trashrOx mentioned would work:
var edus = [
{"School":"Ivy Tech Community College","Email":"ivytech.edu"},
{"School":"Liberty University","Email":"liberty.edu"},
{"School":"Miami Dade College","Email":"mdc.edu"},
{"School":"Lone Star College","Email":"lonestar.edu"},
{"School":"Ashford University","Email":"ashford.edu"}
];
var emails = ['matty.fake#lonestar.edu','himom#mdc.edu','randomguy#yahoo.com'];
emails.forEach(function(element,index) {
var domain = element.substring(element.indexOf('#') + 1);
var match = 'none';
if (domain) {
edus.forEach(function(element,ind) {
if (element.Email === domain) {
match = element.School;
}
});
console.log(element + ' matched ' + match);
}
});
// matty.fake#lonestar.edu matched Lone Star College
// himom#mdc.edu matched Miami Dade College
// randomguy#yahoo.com matched none
Use Array.prototype.some() if you only want to validate.
some() executes the callback function once for each element present in the array until it finds one where callback returns a truthy value (a value that becomes true when converted to a Boolean). If such an element is found, some() immediately returns true. Otherwise, some() returns false. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.
var schools = [
{"School":"Ivy Tech Community College","Email":"ivytech.edu"},
{"School":"Liberty University","Email":"liberty.edu"},
{"School":"Miami Dade College","Email":"mdc.edu"},
{"School":"Lone Star College","Email":"lonestar.edu"},
{"School":"Ashford University","Email":"ashford.edu"}
]
function validate(email) {
var domain = email.split('#').pop();
return schools.some(function(school) {
return school.Email === domain;
});
}
validate('test#ashford.edu');

Text search - filter two words in two different objects/arrays

I'm using angularJS for my webapp and I have the following problem. When I use angularjs' filter to search for, let's say "John Smith", my object, which is built as follow ({firstname: 'John', lastname: 'Smith'}), won't be recognized because the first name and the last name are not part of the same key.
Any idea how to make the space to be either a space or a search in other keys (or arrays)?
EDIT:
Per your requirements, I came up with this:
$scope.compFunc = function(actual, expected) {
var expectedSplit = expected.split(' ');
var fullActual;
//Search people for the person the name belongs to
$scope.names.forEach(function(person){
angular.forEach(person, function(name){
if (name === actual) {
fullActual = person;
}
})
})
//create an array from the names object for comparison
var fullActualArray = [];
angular.forEach(fullActual, function(name, key){
if (key != '$$hashKey')
fullActualArray.push(name = name.toLowerCase());
})
fullActualArray.sort();
expectedSplit.sort();
return actual.toLowerCase().indexOf(expected.toLowerCase()) > -1||
fullActualArray.join(',') === expectedSplit.join(',');
};
This should work just how you expect.
FIDDLE
Note: I used forEach for the sake of clarity, but if you have large amounts of data you may want to change it to for loops, which are 90% faster and can break when a match is found.
Previous solutions:
It's very simple, checks if either the value entered in the search (expected) exists in either firstname or lastname (actual), or the opposite. So entering "Robert Downy" should work, but so will "Robert blargh" or "abcDownyefg". Checking against the whole thing (firstname + lastname) is something that you have to do outside the comparison function, and is not supported out of the box with angular as far as I know.
I've also used the following to create a new array for comparison using the full names:
$scope.fullnames = $scope.names.map(function(name){
return {
firstname: name.firstname,
lastname: name.lastname,
fullname :name.firstname + " " + name.lastname
}
})
While extending the original array can be done like this:
$scope.names.forEach(function(name){
angular.extend(name,{fullname:name.firstname + " " + name.lastname});
})
This is simple and effective, and is what I would do.
You can create your own custom filter, and have it search whatever you want.

DOJO Filtering select RegExp query

I would like to achieve something like this:
filteringSelect.query = {id: "12|13"};
or
filteringSelect.query = {id: new RegExp("12|13")};
Is it possible?
I am using ItemFileReadStore as a store for that FilteringSelect.
See Fuzzy Matches on dijit.form.ComboBox / dijit.form.FilteringSelect Subclass if you want to go the extra mile. This is however for filtering user-input.
For filtering away entries before opening/entering anything in the filteringSelect, continue what youre doing allready. A simple string will not accept the OR operator though, use RegExp.
ItemFileReadStore docs on query
var store = new ItemFileReadStore( {
query: {
id: new RegExp("/^(12|13)$/")
}
} );
As a starting point, ALL items are present in the store, the way to make use of the queryengine is through fetch
store.fetch({
query: {
// yes, you can set the query property directly
// in the store and leave out this parameter
id: new RegExp("^(1|12)$")
},
onComplete: function(items) {
dojo.forEach(items, function(item) {
console.log(store.getValue(item, 'name'))
});
}
})
See http://jsfiddle.net/LuUbT/ for example usage

Categories

Resources