1

I've got an application where I show a list of items with AngularJS. I'm trying to make an easy search on this list but it searches on everything inside that item. An example of an item:

{
    id: 283727893,
    name: 'Item A',
    parent: {
        id: 239495838,
        name: 'Item B'
    },
    user: 'User C'
}

In the list I'm only writing the name of the root item, so parent.name does not appear.

The problem

However, if I search using an AngularJS filter by 'Item B', it still appears, because an inner property has this string.

What I've tried

I've put an object reference such as the following (where val is the text of the input):

vm.searchObj = {
    name: val,
    user: val
};

And then, on my DOM:

<li data-ng-repeat="[...] | filter: Ctrl.searchObj | orderBy: Ctrl.orderBy">
...
</li>

However, this works with conditional AND, so it only shows if both, name and user have val.

What I need

I'd like to take an input text and make a search to filter this list on given object properties (in plural).

So, in this example, this item should appear if I write 'Item A' or 'User C' (or just 'A' or just 'C' and so on), but not if I write 'B', because none of name and user have this letter.

I'd need it to take the reference object, in this case Ctrl.searchObj, and check if any of the given properties of the object contains val into the same structure of the filtered objects.

That, of course, should work into deeper levels of the object, so I could define the searchObj as follows and still be able to get if the filtered object has that structure inside:

vm.searchObj = {
    parent: {
        name: 'Item'
    }
};

In this case, it should show those items where its parent.name property contains the word 'Item'.

If you need more information, please, let me know!

Thank you!

Unapedra
  • 2,043
  • 4
  • 25
  • 42
  • You need to create a custom filter. This has already been answered here: https://stackoverflow.com/a/26555044/4888735 – CodeWarrior May 25 '17 at 16:05
  • In fact, that answer only gives the possibility to make a first-level properties comparison, and providing a single value. As I say on "What I need", I need to take the structure of the `searchObj` and check if the object filtered has the same structure (and, of course, **contains** the `val` as the value of that structure). – Unapedra May 25 '17 at 16:09
  • I updated my question so @CodeWarrior can see to what I refer. Thank you! – Unapedra May 25 '17 at 16:13
  • Can you please @CodeWarrior take off the duplicate tag? Thank you. – Unapedra May 26 '17 at 08:12
  • How many levels deep does your object go? Just 1 parent? Or can the parent itself have its own parent? Also, if a parent is a match, do you want to display the root name or the parent's name? – CodeWarrior May 26 '17 at 10:33
  • Can have its own parent. I'd like to search for different type of objects, so they may have different structure and, in deed, different levels of deep. This is the main handicap, to be able to adapt to the object structure. Maybe some function like "Object contains object structure?" could be the start, but I cannot find the way to think of a solution... Thank you again! – Unapedra May 26 '17 at 10:35
  • Just to confirm, you need to be able to search by two fields, name and user, correct? Will you be searching by both fields together? Or only one of those two fields at a time? – CodeWarrior May 26 '17 at 10:36
  • I need to search by multiple fields. In this case, they're just two properties, but maybe I would need to search by three or four or... The only thing they have in common is that they will be searching for the same value (`val`) and that, in case **any of them** contains `val` then it should return `true` (because it matches the search criteria). But the number of properties and its deep must be adaptable. This is why it's so hard for me to find a solution! Thank you for your help! – Unapedra May 26 '17 at 10:38
  • Yes! Because of this I think the "Object contains object" perspective can be a start, this way I can create an object with the structure (fields) in which I will search for `val`. – Unapedra May 26 '17 at 10:41

3 Answers3

1

If I understand correctly, an object being searched (we'll call it target) is a match if:

  • both searchObj and target share at least one property name at the same level

  • The value of target's property is equal to or contains the value of searchObj's property

The code below (which includes your edits) should do what you need. I'll leave it to you to fill in any edge cases (checks for hasOwnProperty, comparison of different types, etc.). I think filtering like this should only happen when necessary, like when a user types. Creating an Angular filter out of this could be too expensive, since it runs on each digest cycle:

function objectContains(searchObj, target) {
    for (var property in searchObj) {
        var searchVal = searchObj[property];
        var targetVal = target[property];

        if (isObject(searchVal) && isObject(targetVal)) {
            if(objectContains(searchVal, targetVal)){
                return true;
            }
        }

        if (
            !isObject(searchVal) && !isObject(targetVal) && 
            !isUndefinedOrNull(targetVal) && 
            !isUndefinedOrNull(searchVal) &&
            targetVal.toString().indexOf(searchVal.toString()) !== -1
        ) {
            return true;
        }
    }

    return false;
}

function isUndefinedOrNull(x) {
    return typeof x === "undefined" || x === null;
}

function isObject(x) {
    return typeof x === "object";
}

var filtered = list.filter(function(item) {
    return objectContains(searchObj, item);
});
Frank Modica
  • 10,238
  • 3
  • 23
  • 39
  • Thank you so much! It's what I needed, but it returns `false` if the first `property` does not match. For example, if I search for `{user: {name: 'AL'}, admin: {name: 'AL'}}` (so, if any of `user` or `admin` name contains 'AL' should return `true`) and it does not match with `user` name, it doesn't check for`admin` property, and returns false. I'm trying to modify it to check it. As soon as I do, I'll mark your answer as correct. Thank you for your help! – Unapedra May 29 '17 at 09:02
  • Done! I've made some modifications. My answer is the one that works in my scenario, but it's your answer modified, so I'll mark yours as the correct one! Thank you so much! – Unapedra May 29 '17 at 09:14
  • 1
    Yes you are right, I didn't test my code against the case of multiple object properties at the same level, and the case of one property at a level being an object while the other isn't. I updated my answer with your fixes. – Frank Modica May 29 '17 at 14:22
0

Try this way. Use true property on filter to get exactly what you search!

<li data-ng-repeat="[...] | filter: Ctrl.searchObj:true | orderBy: Ctrl.orderBy">
...
</li>
LXhelili
  • 980
  • 5
  • 14
0

So, taking as the base the code by Frank Modica (thank you), I've come up with some editions on its compare function to look up not only for the first property, but if this does not match, keep up searching. This is the code modified:

function objectContains(searchObj, target) {
    for (var property in searchObj) {
        var searchVal = searchObj[property];
        var targetVal = target[property];

        if (isObject(searchVal) && isObject(targetVal)) {
            if(objectContains(searchVal, targetVal)){
                return true;
            }
        }

        if (
            !isObject(searchVal) && !isObject(targetVal) && 
            !isUndefinedOrNull(targetVal) && 
            !isUndefinedOrNull(searchVal) &&
            targetVal.toString().indexOf(searchVal.toString()) !== -1
        ) {
            return true;
        }
    }

    return false;
}

In case it matches, it returns true because it needs only one match to be what we are looking for. In case it does not match, it keeps going.

We check for the second condition not to be an object neither of the values because they turn to [object OBJECT] when we apply toString(), so it returns true always. This way, if it's an object, it will ignore it (no need to do further checkings because we already done that in the first if).

Thank you Frank because I couldn't come up with this!

Now, it works perfectly!

Unapedra
  • 2,043
  • 4
  • 25
  • 42