Underscore js easy javascript object hierarchical filtering

Hi,

Just want to share something that in my opinion is really a useful helper library, and that is underscore.js

When combined with knockout, you can achieve easy filtering through its helper functions.

Let's say we have an object structure like this:

Company A
  - Boss A
      - Supervisor A
          - Employee A
          - Employee B
          - Employee C
      - Supervisor B
          - Employee C
          - Employee D
  - Boss B
      - Supervisor C
          - Employee A
          - Employee C

Company B
   etc..

and after you are getting the all records, you want to provide some filtering based on the Boss, Supervisor or Employee. (to make it simple I just use dropdowns)

Using underscore, I can grab the list of unique values of Boss, Supervisor and Employee like this:

1
2
3
BossList= _(data).chain().pluck('Boss').flatten().unique('name').sortBy('name').value();
SupervisorList= _(BossList).chain().pluck('Supervisor').flatten().unique('name').sortBy('name').value();
EmployeeList= _(SupervisorList).chain().pluck('Employee').flatten().unique('name').sortBy('name').value();
After that, if you are using knockout then you can bind the list to a dropdown, and using the selected value of the observable you can then create filter functions your original data:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function FilterBoss(data, bossId)
{
    return _.filter(data, function (c) {
        c.Boss= _.filter(c.Boss, function (b) {
            return b.id == bossId;
        });
        return c.Boss.length > 0;
    });
}
 
function FilterSupervisor(data, supervisorId)
{
    return _.filter(data, function (c) {
        c.Boss = _.filter(c.Boss, function (b) {
            b.Supervisor = _.filter(b.Supervisor, function (s) {
                return s.id == supervisorId;
            });
            return b.Supervisor.length > 0;
        });
        return c.Boss.length > 0;
    });
}
 
function FilterEmployee(data, employeeId)
{
    return _.filter(data, function (c) {
        c.Boss = _.filter(c.Boss, function (b) {
            b.Supervisor = _.filter(b.Supervisor, function (s) {
                s.Employee = _.filter(s.Employee, function (e) {
                    return e.id == employeeId;
                });
                return s.Employee.length > 0;
            });
            return b.Supervisor.length > 0;
        });
        return c.Boss.length > 0;
    });
}
Then you will just need to have a computed observable to return your filtered data:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
viewModel.FilteredData = ko.computed(function () {
    var selectedBoss = this.SelectedBoss();
    var selectedSupervisor = this.SelectedSupervisor();
    var selectedEmployee = this.SelectedEmployee();
 
    if (selectedBoss) {
        var origData = JSON.parse(JSON.stringify(data)); //Clone to leave original data alone
        var filterObj = FilterBoss(origData, selectedBoss.id);
 
        if (selectedSupervisor) {
            filterObj = FilterSupervisor(filterObj, selectedSupervisor.id);
 
            if (selectedEmployee) {
                filterObj = FilterEmployee(filterObj, selectedEmployee.id);
            }
        }
 
        if (selectedEmployee) {
            filterObj = FilterEmployee(filterObj, selectedEmployee.id);
        }
 
        return ko.mapping.fromJS(filterObj)();
    }
    else if (selectedSupervisor) {
        var origData = JSON.parse(JSON.stringify(data));
        var filterObj = FilterSupervisor(origData, selectedSupervisor.id);
 
        if (selectedEmployee) {
            filterObj = FilterEmployee(filterObj, selectedEmployee.id);
        }
 
        return ko.mapping.fromJS(filterObj)();
    }
    else if (selectedEmployee) {
        var origData = JSON.parse(JSON.stringify(data));
        var filterObj = FilterEmployee(origData, selectedEmployee.id);
 
        return ko.mapping.fromJS(filterObj)();
    }
    else {
        return this.Data();
    }
 
}, viewModel);

Hope this helps,

Andreas

Comments

Popular posts from this blog

SharePoint 2013 anonymous access add attachments to list item

CRM Plugin - Parent and Child Pipeline