0

I need a function to filter my array and return results based on user inputs.

var Myarray= [
  {Date:"1-Jan-2020",Name:"A", Id: 1},
  {Date:"1-Feb-2020",Name:"B", Id: 2},
  {Date:"1-Mar-2020",Name:"C", Id: 3}
  ...
]

The filter needs to be dynamic. User MUST input at least Date or Name or both together. ID is optional

Is there a smart way to build the filter expression in the function based what ever inputs the user inputs?

More info: I have a UI. The user will have search parameters to choose from for returning date. Date, Name and ID. The user MUST choose at least The Date or Name. The ID is optional but must be considered in the function that will accept the parameters sent. After the users inputs the values, they are passed to a JS function that should filter an array that holds the data. After the filters are applied to the array, a new array is filled with the filtered data. Ex.user sends Date without name or or ID, Then array only filters based on date. Ex2. user sends Name without date or id, then array only filters based on name. Ex3. user sends date and name without id, then array filters based on date and name. Ex4. user sends Date and id without name, then array filters based on date and id. Ex5. user sends name and id without date, then array filters by name and id. ex6 user sends date and name and id, then array filters based on date and name and id. Ex7 user sends id, funtction will NOT allow id to sent alone without either date or name

  • Sharing your research helps everyone. Tell us what you've tried and why it didn’t meet your needs. This demonstrates that you’ve taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer! Also see [ask]. – slumbergeist Apr 10 '20 at 19:49

3 Answers3

1

You can use Array.prototype.some (OR) to chain predicates. If at any point a predicate matches, the entire item will be added to the resulting list.

If you want all conditions to match, you will need to use Array.prototype.every (AND) chaining.

I may come back to this, with a more dynamic example, shortly. See below.

const main = () => {
  let arr = [
    { Date: "1-Jan-2020" , Name: "A"  , Id: 1 }, // Yes
    { Date: "1-Feb-2020" , Name: "B"  , Id: 2 }, // Yes
    { Date: "1-Mar-2020" , Name: null , Id: 3 }, // Yes
    { Date: null         , Name: null , Id: 4 }  // No
  ]
  
  console.log(filterWithPredicates(arr, predicates))
}

const predicates = {
  date: (record) => record.Date != null,
  name: (record) => record.Name != null
};

const filterWithPredicates = (list, predicates) => {
  return list.filter(item => {
    return Object.values(predicates).some(predicate => {
      if (predicate(item)) { return true }
    })
    return false
  })
}

main()
.as-console-wrapper { top: 0; max-height: 100% !important; }

Here is an example of predicate chaining.

const Predicates = {
  date: (record) => record.Date != null,
  name: (record) => record.Name != null
}

const main = () => {
  let arr = [
    { Date: "1-Jan-2020" , Name: "A"  , Id: 1 }, // Yes
    { Date: "1-Feb-2020" , Name: "B"  , Id: 2 }, // Yes
    { Date: "1-Mar-2020" , Name: null , Id: 3 }, // Yes
    { Date: null         , Name: null , Id: 4 }  // No
  ]
  
  let filter = new Filter().chain().or(Predicates.date).or(Predicates.name)
  console.log(filter.execute(arr))
}

class Filter {
  constructor() {
    this.filters = []
  }
  chain() {
    this.filters = []
    return this
  }
  or(predicate) {
    this.filters.push({ fn : predicate, op : 'or' })
    return this
  }
  and(predicate) {
    this.filters.push({ fn : predicate, op : 'and' })
    return this
  }
  execute(items) {
    return items.reduce((results, item) => 
      this.__shouldKeepItem(item) ? results.concat(item) : results, [])
  }
  /** @private */
  __startCondition() {
    return this.filters.length ? this.filters[0].op === 'and' ? 1 : 0 : 0
  }
  /** @private */
  __shouldKeepItem(item) {
    return this.filters.reduce((keep, filter) => {
      switch (filter.op) {
        case 'or'  : return keep || filter.fn(item)
        case 'and' : return keep && filter.fn(item)
        default    : return keep
      }
    }, this.__startCondition())
  }
}

main()
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • Your solution seems good however I am quite new to JS and don't understand it much. Also I cant see how you handled the optional Id parameter? I will wait for your more dynamic example – Mutasem Sallam Apr 10 '20 at 20:15
  • @MutasemSallam If the ID is optional, then you do not filter on it at all. – Mr. Polywhirl Apr 10 '20 at 20:29
  • Can you inline comments in your code on how each line works? I cant understand it. yes the id is optional but the user can still choose it if he wishes to. So it must be consider if it parameter is not null – Mutasem Sallam Apr 10 '20 at 20:39
  • i added more information to my question. please check it – Mutasem Sallam Apr 10 '20 at 22:29
0

Try:

var filteredArray = Myarray.filter(function (item) {
   return item.Date !== undefined && item.Name !== undefined;
});
var filteredArrayWithOptionalId = Myarray.filter(function (item) {
   return item.Date !== undefined && item.Name !== undefined && item.Id !== undefined;
});

You now have 2 arrays, one with the required parameter objects and one that can handle the optional ID. Then, you can loop through the filteredArrayWithOptionalId to handle the code relating to IDs.

See the answer to a similar question here: How to filter object array based on attributes?

Also I recommend lowercasing your variable names because it is best practice, as long as they are not classes.

Elijah Mock
  • 587
  • 8
  • 21
0

const Myarray= [
  {Date:"1-Jan-2020",Name:"A", Id: 1},
  {Date:"1-Feb-2020",Name:"B", Id: 2},
  {Date:"1-Mar-2020",Name:"C", Id: 3}
];

const customFilter = (array, expr) => {
  if (Object.keys(expr).length == 0) {
    console.log("Error! missing at least one filter expression");
    return null;
  }
  /* if expression doesn't exist it's a pass for all items */
  return array.filter(item => !expr.Date || item.Date == expr.Date).filter(item => !expr.Name || item.Name == expr.Name).filter(item => !expr.Id || item.Id == expr.Id)
}

console.log(customFilter(Myarray, {Date: "1-Jan-2020", Name: "B"}))
console.log(customFilter(Myarray, {Date: "1-Jan-2020"}))
console.log(customFilter(Myarray, {Name: "B"}))
console.log(customFilter(Myarray, {Id: 3}))
console.log(customFilter(Myarray, {}))
Wildhammer
  • 2,017
  • 1
  • 27
  • 33
  • I don't need a validation message log. The filter expression should be built dynamically for whatever the user inputs based on the conditions I provided in the summary. Also, The Id is optional however it must be handled in the function if the user wishes to use it. – Mutasem Sallam Apr 10 '20 at 20:12
  • You need to be more specific when posting a question. Provide a few example input/output pairs. There are many different ways one can interpret what you are asking in terms of coding. – Wildhammer Apr 10 '20 at 21:21
  • I have a UI. User will have search params to choose for returning data.Date, Name and ID.User MUST choose at least Date or Name.ID is optional but must be considered in function that accepts params sent.After user inputs values, they are sent to JS function that filters array that holds data. After filters are applied to the array, new array is filled with filtered data. Ex.user sends Date without name orID, Then array only filters on date.Ex2.user sends Name without date or id, then array only filters on name.Ex3 user sends id, funtction will NOT allow id to be sent alone without date or name – Mutasem Sallam Apr 10 '20 at 22:26