AndreFontaine
AndreFontaine

Reputation: 2584

Angular custom filter to complex object

I have a $scope.users that I use to show a table and I need to apply a filter with multiple options,

Here is the $scope.users object:

[
  {
    "login": "[email protected]",
    "merchant": {
      "merchantId": 500095,
      "status": "ENABLED",
      "defaultAccountId": null,
      "accounts": {
        "500092": "ADMIN",
        "500178": "CONSULT",
        "500180": "ADMIN",
        "500182": "ADMIN",
        "500201": "ADMIN"
      }
    },    
    "name": "Carlos"    
  },
  {
    "login": "[email protected]",
    "merchant": {
      "merchantId": 500095,
      "status": "DISABLED",
      "defaultAccountId": null,
      "accounts": {
        "500092": "CONSULT",
        "500178": "MANAGER",
        "500180": "ADMIN",
        "500182": "ADMIN",
        "500201": "ADMIN"
      }
    },
    "name": "Carlos Andres"
  },
  {
    "login": "[email protected]",
    "merchant": {
      "merchantId": 500095,
      "status": "DISABLED",
      "defaultAccountId": null,
      "accounts": {
        "500092": "CONSULT",
        "500178": "MANAGER",
        "500180": "ADMIN",
        "500182": "ADMIN",
        "500201": "ADMIN"
      }
    },
    "name": "Cosme"    
  }
]

In the filter I pass other object from the $scope to filter the data, in this case is $scope.searchParams that I previously calculate in a directive.

$scope.searchParams:

[
  {'name':'Cosme'},
  {'login':'[email protected]'},
  {'status':'ENABLED'},
  {'status':'DISABLED'},
  {'account':'267123'},
  {'account':'543987'},
  {'account':'544111'},
  {'account':'543987'}
]

As you can see, name and login are unique in the filter, but status and account can have multiple values to apply in the filter. Here is my approach to do that but I think is very ineffective, also is not complete because doesn't have the multiple filter functionality:

var secUser = function () {

  return function (input, list) {

    var out = [];

    var nameArr = [],
      loginArr = [],
      statusArr = [],
      profileArr = [],
      accountsArr = [];

    angular.forEach(list, function(filter){
      console.log(filter);
      if(filter.hasOwnProperty('name')){
        nameArr.push(filter.name);
      }
      if(filter.hasOwnProperty('login')){
        loginArr.push(filter.login);
      }
      if(filter.hasOwnProperty('status')){
        statusArr.push(filter.status);
      }
      if(filter.hasOwnProperty('account')){
        accountsArr.push(filter.account);
      }
      if(filter.hasOwnProperty('profile')){
        profileArr.push(filter.profile);
      }
    });

    angular.forEach(input, function(user){

      // no filters applied
      if(nameArr.length < 1 && loginArr.length < 1 &&
        statusArr.length < 1 && accountsArr.length < 1 &&
        profileArr.length < 1){
        out.push(user);
      }
      // filters to be applied
      else{
        if(nameArr.indexOf(user.name) > -1){
          out.push(user);
        }
        if(loginArr.indexOf(user.login) > -1){
          out.push(user);
        }
        if(statusArr.indexOf(user.merchant.status) > -1){
          out.push(user);
        }
      }

    });

    return out;
  };
};
//-- register filter
app.filter('secUser', secUser);

Finally here is how I am using the fiter: <tbody ng-repeat="user in users | secUser:searchParams " class="table-striped">

¿Is there a better way to implement this filter considering that in the future there can be more filter fields? Also I think is very coupled with the view.

Upvotes: 4

Views: 507

Answers (1)

sheilak
sheilak

Reputation: 5873

There are multiple ways you could do this but one requirement is you need to make your searchParams structure more generic. For example, here I'm using an object where each key is the path to the target object and the corresponding value is an array with all the values you want to match against:

$scope.searchParams = {
  'name': ['Cosme'],
  'login': ['[email protected]'],
  'merchant.status' : ['ENABLED','DISABLED'],
  'merchant.accounts' : ['267123','543987 ','544111','543987'],
}

Then you can update your filter to handle generic data. It can't be completely generic because it does depend on your business logic, e.g. does accounts match on the key 500092 or the value CONSULT. Here is an updated filter function handling the three kinds of targets in your data:

  • primitive values match the value e.g. name
  • arrays match any of the contents e.g. status
  • objects match any of the keys e.g. accounts

JsFiddle (with data slightly modified for testing)

var secUser = function () {
  return function (users, filters) {

    return users.filter(function(user){
      var match = false;

      angular.forEach(filters, function(filterValues, keyPath){
        if (match) return; // if we already found a match, don't continue searching

        // get the target based on the contents of the keypath
        var target = keyPath.split('.').reduce(function(object, key) {
          // get the next part of the path, if exists
          return object && object[key];
        }, user); // initial value is the user object

        if (target !== undefined){ // if the target exists for this item
          var values = []; // stores the values from the item to search against

          if(typeof target === "object") {
            values = Object.keys(target);            
          }
          else if (target.isArray) {
            values = target;
          } else {
            values = [target];
          }

          // check if any entry in values matches any entry of the filter values
          match = values.some(function (v) {
            return filterValues.indexOf(v) >= 0;
          });
        }
      });

      return match;
    });
  };
};

Upvotes: 1

Related Questions