AngularJS server-side multi-column search - angularjs

I am building an application in NodeJS and AngularJS.
I am building a multi-column search functionality where the user can type in search keywords into separate searchboxes (at the top of each column) and retrieve the results based on the column.
So far I have a single searchbox that searches all attributes at the same time.
How can I implement multiple individual searchboxes that will return results based on multiple attributes?
Note: I want to implement this on the server-side for performance reasons. (I know that I can simply use HTML attributes | filter:column1 | filter:column2 but want to avoid this technique if possible).
Here is the code I have so far. I am thinking that I need to pass in some sort of "searchBy" variable that is set on the view and then update the search method to search by multiple query/attribute pairs.
//Search service factory
//Initialize filtered items and get search results
function search(items, query) {
this.filteredItems = $filter('filter')(items, function (item) {
for(var attr in item) {
if (searchMatch(item[attr], query))
return true;
}
return false;
});
return this.filteredItems;
}
function searchMatch(haystack, needle) {
if (!needle) {
return true;
}
return haystack.toString().toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};
//Controller
vm.filteredItems = vm.search(vm.unfilteredItems, vm.query);
//View
input(type='text', ng-model='vm.query', ng-change='vm.search(vm.unfilteredItems, vm.query)', placeholder='Search')

I was able to solve this by first creating an array of objects for each search box then repeating those boxes in the view with the ng-repeat attribute.
//Controller
var vm = this;
var vm.unfilteredItems; //data source query removed for brevity
//Initialize search inputs
vm.search_by_inputs = [
{search_column: 'id', search_query: ''},
{search_column: 'requester', search_query: ''},
{search_column: 'dataowner', search_query: ''}
];
function initSearch() {
vm.filtered_items = vm.search(vm.unfiltered_items, vm.search_by_inputs);
}
//View
input.input-large.search-query(type='text', value='{{search_by.search_query}}', ng-model='search_by.search_query' ng-change='vm.initSearch()', placeholder='Search')
The next step is to loop over the search_by_inputs object in the controller and create a new object with only the inputs that have search values entered into the searchboxes in the view. Then in the search method the built-in "filter" component iterates each item, and inside that loop each of the search terms is checked against that value with the column name that matches the property.
/*
* Create new array of objects with only elements that have search values to optimize loop inside filter
* #search_by_inputs array of objects each has a key search_column and a value search_query
*/
function optimizeSearchProperties(search_by_inputs) {
search_by_properties = [];
for (var i = 0, len = search_by_inputs.length; i < len; i++) {
//If this column input box has query text
if (search_by_inputs[i].search_query) {
search_by_properties.push(search_by_inputs[i]);
}
}
return search_by_properties;
}
/*
* #haystack search item
* #needle search term
*/
function searchMatch(haystack, needle) {
if (!needle) {
return true;
}
return haystack.toString().toLowerCase().indexOf(needle.toLowerCase()) !== -1;
}
/*
* Create filtered items object by filtering search results
* #items original array of objects returned by database query result
* #search_by_inputs array of objects each has a key search_column and a value search_query
*/
function search(items, search_by_inputs) {
var search_by_properties = optimizeSearchProperties(search_by_inputs);
//If there are no search properties input by requester then return all items
if (search_by_properties.length === 0) {
this.filtered_items = items;
return this.filtered_items;
}
this.filtered_items = $filter('filter')(items, function (item) {
var search_result = true;
//Loop over all search by input textboxes
for (var n = 0, len = search_by_properties.length; n < len; n++) {
//If there is no query text
if (!search_by_properties[n].search_query) {
//Continue to next element in array
continue;
//Else if element has a property that matches search input column name
} else if (item[search_by_properties[n].search_column]) {
if (!searchMatch(item[search_by_properties[n].search_column], search_by_properties[n].search_query)) {
search_result = false;
break;
}
}
}
return search_result;
});
return this.filtered_items;
}
I would be glad to have some feedback on this solution in terms of optimization, performance, technique, etc. Thanks!

Related

AngularJS mysql filter multiple element

Hi this is my hotel project. But I'm having a problem with a filter.
I want to filter the data in the amenities column.
This is: my fiddle
It works if you select a single checkbox but it does not work if you select multiple checkboxes.
I suspect the problem arises from indexof instead of what I can use. What method should I follow?
How to change this line: indexOf(x);
This is my bad code:
//PROBLEM FILTER HERE
$scope.am_en = function()
{
//get input value
x = $(".hosting_amenities input:checkbox:checked").map(function(){return $(this).val();}).get();
//filter
$scope.ot_Filter = function (location) {
return location.amenities.indexOf(x) !== -1;
};
}
The problem is indeed caused by the function $scope.am_en, in the declaration of the inner function $scope.ot_Filter
When you select multiple checkboxes, your x variable is an array of objects, so you should do a loop and can create a variable to check whether the element should be shown or not. You can do it as follows:
$scope.ot_Filter = function (location) {
var shouldBeShown = false;
for (var i = 0; i < x.length; i++) {
if (location.amenities.indexOf(x[i]) !== -1) {
shouldBeShown = true;
break;
}
}
return shouldBeShown;
};
I modified your jsfiddle, so that you can see this solution working properly.

Angular ng-repeat filtering

I have a deeply nested object. I have some records which contain 2 fields that show keys of object properties. I also have select needed to search records by property of object and input to search by key of object. So if I choose option1 and type in input some text, it will be shown the matches in the first field (not second!). And it's similar for second field.
How I try to realize:
I wrote a filter http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
.filter('appFilter', function() {
return function(value, select, input) {
var result = [];
input = input.toLowerCase();
var reg = new RegExp(input,'g');
if (angular.isArray(value)) {
if (input === '' || $scope.isFiltering) {
return value;
} else if (select.value === 'Sequence') {
for (let i = 0; i < value.length; i++) {
if (value[i].Sequence.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
return result;
} else if (select.value === 'ID') {
for (let i = 0; i < value.length; i++) {
if (angular.isArray(value[i].Document)) {
for (let j = 0; j < value[i].Document.length; j++) {
if (value[i].Document[j].ID.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
}
}
return result;
} else {
console.log('error');
}
}
}
})
In controller I set to select's ng-model first option: $scope.selectParameter = $scope.parameter[0];
In debug I set to input parameter some value (123 for example).
So I searching record by first field that contains 123 value. And result finds and pushes the object. But in browser shows anything.
What's the problem? And I can't avoid the empty option with '?' value in my select :(
UPDATED
Nearly solve my problem: http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
It filters by appropriate field and input value. But I faced with another troubles.
When input is empty it doesn't show any record. And second is when I choose second option (ID) filter duplicates some records.
Also I try to switch off filter without clearing the input text by clicking on checkbox.
It's what I want to do but it doesn't work:
else if (input === '' || $scope.isFiltering) {
return value;
}
$scope.isFiltering is ng-model for checkbox input
I tried using angulars default filter. I'm not sure if this is exactly what you want, but maybe it helps a little.
.filter('appFilter', function($filter) {
return function(value, select, input) {
if( !angular.isDefined(input) || input.length < 1) {
return value;
}
// Angulars "filter" lets you pass in a object-structure to search for nested fields.
var query =
(select.value === 'Sequence') ?
{Sequence:input} : {Document:{ID:input}};
return $filter('filter')(value, query);
}
})
http://plnkr.co/edit/Egkw9bUvTPgooc0u2w7C?p=preview

How can I use "ui-scroll" with my own data?

I'm trying to create a infinite scroll feature in my application but it feels a bit abstract. I want to use ui-scroll and this fiddle shows a simple example of how it works.
I've read the readme and looked through some examples also I've integrated the example in my project and got it working, but I can't figure out on how to combine this with data from my own database.
I have a database table called movies. The movies have a few values such as title, release_date, image_url
How would I insert that data into the $scope.movieDataSource so I can use it in my view?
$http.get(('/movies.json'), {
cache: true
})
.success(function(data, status, headers, config) {
if (status == 200) {
$scope.userMovies = data;
} else {
console.error('Error happened while getting the user list.')
}
$scope.movieDataSource = {
get: function(index, count, callback) {
var i, items = [$scope.userMovies], item;
var min = 1;
var max = 1000;
for (i = index; i < index + count; i++) {
if (i < min || i > max) {
continue;
}
item = {
title: $scope.userMovies.title,
imageURL: $scope.userMovies.poster_path
};
items.push(item);
}
callback(items);
}
}
});
I've tried to create an example of what I'm trying to get at. I use a http.get to fill my userMovies scope with records from my database and I want to use those records as items in the movieDataSource.
But when I visit the page I that ui-scroll does add results in the container, but it does not show content.
<div class="imageCell ng-scope" ui-scroll="item in movieDataSource">
<img title="">
</div>
If I console.log("movieDataSource" + $scope.movieDataSource) it shows me movieDataSource[object Object].
You are making this more complex than necessary. The uiScroll directive is a replacement for ngRepeat, which takes a Data Source with 3 properties:
index indicates the first data row requested
count indicates number of data rows requested
success function to call when the data are retrieved. The implementation of the service has to call this function when the data are retrieved and pass it an array of the items retrieved. If no items are retrieved, an empty array has to be passed.
in your case, you have an array of items. Each time the index or count changes, the success fires, and this function should return a subset of your array from index to index + count. There are multiple ways to accomplish this. The example you posted uses a for loop to iteratively push items into the array. You could also use the Array.slice() method.
Option 1:
$scope.movieDataSource = {
get: function(index, count, callback) {
var i, items = [], item;
for (i = index; i < index + count; i++) {
item = $scope.userMovies[i];
items.push(item);
};
callback(items);
}
}
Option 2:
$scope.movieDataSource = {
get: function(index, count, callback) {
var items = $scope.userMovies.slice(index, index + count);
callback(items);
}
}
As for your HTML, it should be identical to if you were using ng-repeat:
<div ui-scroll="item in movieDataSource">
{{item.title}}
<img title="{{item.title}}" ng-src="{{item.poster_path}}"></img>
</div>
Apparently ui-scroll calls the given object "movieDataSource" with an index and a count. It then expects the function to push all items between index and index + count in the returned array.
That means that you have to implement code that fetches the respective items from your DB (via REST or however you access your data) and insert the returned records in the items array.

Angular: How do i check if the select-element is multiple and set a selected option?

An Angular Service, responsible to build URLs from values of select and multi-select elements drives me crazy.
A multi-select uses an array to store its selected values, even if there is only one element.
{
"select006": [
"m30x1.5",
"m18x1"
]
}
{
"select006": [
"m30x1.5"
]
}
A single select uses simply an string to store its selected value.
{
"select006": "m30x1.5"
}
How do i deal with that issue? How do i check if the select-element is multiple or not and set the selected option to the specific value(s)?
Here is the relevant code:
/**
* Expand multiple values
*
* Convert param values back to an object with array
* of values.
*
* #example ?param=m30x1.5,m18x1 becomes {"select006": ["m30x1.5","m18x1"]}
*
* #param filters
* #returns {object}
* #private
*/
var _expandMultipleValues = function (filters) {
var param = {};
for (var filter in filters) {
if (filters.hasOwnProperty(filter)) {
if (filters[filter].indexOf(',') > -1) {
param[filter] = filters[filter].split(',');
} else {
// multiple needs to be an array to set selected option
// param[filter] = [filters[filter]];
// singe needs to be an string to set selected option
// param[filter] = filters[filter];
}
}
}
return param;
};
It seems i need to be more specific. The Problem here is that i need an array to set a selected option for multiple selects (even if only one is selected) but a string for a single select.
I've a variable in $scope wich holds the selected elements:
$scope.filter = {
"SA152": ["lorem", "ipsum"],
"SA044": ["30mm"],
"SA034": "m8x3"
}
If i try to set the selected option for SA044 with "SA044": '30mm' the multiple select won't be selected as in angular a multiple selection needs to be an array.
This sets the selected option for a multi select:
$scope.filter = {
"SA044": ['30mm']
}
This does not work for a multi select:
$scope.filter = {
"SA044": "30mm"
}
Vice versa for single select elements.
Well, you can check if a variable is an array or string using instanceOf or typeOf or .constuctor: See here http://tobyho.com/2011/01/28/checking-types-in-javascript/
if(myVariable instanceOf Array){
//multi select
} else if(myVariable.constructor === String){
//single select
}
or you can simply convert your string to an array and check the length of the array.
$scope.myVariable = $scope.myPreviousVar.split(','); // or however you want to delimit this
if($scope.myVariable.length === 1){
//single select
} else if($scope.myVariable.length > 1){
//multi select
}
Assuming that your input could be:
$scope.data = {
"select006": "m30x1.5"
}
or
$scope.data = {
"select006": [
"m30x1.5"
]
}
Check if the value of select006 is Array. if so, it's a multi-select otherwise, it's a single select.
if ($scope.data.select006 instanceof Array)
{
// this is a multi-select,
// $scope.data.select006 is an array of selections
}
else
{
// this is a single-select,
// $scope.data.select006 is the single selection value
}

How to optimise the $filter in angularjs?

Is there a way to configure/work with the angular $filter so that you can pass data to it gradually an not have it always iterate over the whole data but just the previous data filtered ?
I am currently trying to split the a user provided string to run multiple sequential filers on the data. So if I have a data like so: [{name:foo, city:bar},...x250] I could type in the search box "Paris" from that point I should be iteration only on the place in Paris and then type Hotel and then Hilton.... The problem is the filter runs each and every time on the whole data set... Does anyone know how to run the filter sequentially in angular ?
updates...
http://jsfiddle.net/Bretto/ZM4Qa/12/
.filter('searchFilter', function ($filter) {
return function (array, q) {
console.log('data-entry: ', array.length);
var filter = $filter('filter');
var lookFor = function (word, data, cb) {
var dataRes = filter(data, word);
cb(dataRes);
}
var search = function (index, words, data, cb) {
var word = words[index];
lookFor(word, data, function (dataRes) {
console.log('data-current: ', dataRes.length);
if (index < words.length - 1) {
index += 1;
search(index, words, dataRes, cb);
} else {
cb(dataRes);
}
});
}
if (q && q.text) {
var words = q.text.split(' ');
var index = 0;
if(array){
//console.log(array.length);
}
search(index, words, array, function (dataRes) {
array = dataRes;
});
}
return array;
}
})
So this is an attempt to create a filter that gradually iterates over the previous filtered results (type multiple words separated by a space in the input...) the issue is that the filter always starts from the whole data.
is this clearer now ?
Please use multiple filters, so you don't have to go tweaking everything
Take this example as a starting point
http://jsfiddle.net/ed9A2/1/
<tr ng-repeat="player in playersFound = (players | filter:{id: player_id, name:player_name, age:player_age})">

Resources