0

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.

enter image description here

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')

1 Answer 1

1

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!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.