2

I've been working on making my javascript app more scaleable. The app uses knockout.js to bind a grid of some type of database item to be editted and updated by the user. I'm now going down the path of working with inheritance and having a BaseModel and models that inherit from that. The problem I'm having comes in when I want to override a computed observable that is defined in the BaseModel by the class the inherits from it. Heres the use case.

Base grid has a computed observable that the UI binds to. The computed observable works off an observable array with a text box filter that the user can type to search with. Pretty basic.

The child class, roles, is a specific type of grid and part of the business requirement was to have a filter for the department. This is a drop down in the UI. I want to override the computed observerable in the base class with the roles implementation in the child class. Not sure how to accomplish that.

Here is a JSFiddle: http://jsfiddle.net/Icestorm0141/vx6843pt/6/

The BaseGridViewModel:

var BaseGridViewModel = function (serverData) {
    var self = this;    

    //searchTerm is a text box that the user types to search
    self.searchTerm = ko.observable();

    //items are the data from the server
    self.items = ko.mapping.fromJS(serverData, mapping);

    //filteredItems are the filtered list of items that the grid is actually bound to
    //thus when the user searches, the UI updates automatically
    self.filteredItems = ko.computed(function () {
        if (self.searchTerm() == null || self.searchTerm() == "" || self.searchTerm().length < 3) {
            return self.items();
        } 
        else {
            return ko.utils.arrayFilter(self.items(), function (item) {
                 return item.Description().toLowerCase().indexOf(self.searchTerm().toLowerCase()) >= 0;
            });
        }
    });
}

The Child Class:

var RoleGridModel = function (roleData) {
    var self = this;

    //calling the base model, I think this is the proper way to do this?
    BaseGridViewModel.call(self, roleData);

    //UI has a drop down list of departments to filter by, this is the selected filter
    self.departmentFilter = ko.observable();

    //I WANT to set the filter function for the items to something completely different
    var filterOverride = function () {
        if (self.departmentFilter() == null || self.departmentFilter() == "") {
            return self.items();
        }
        else {
            return ko.utils.arrayFilter(self.items(), function (role) {
                return role.DepartmentId() == self.departmentFilter();
            });
        }
    };
}

I've been doing a lot of research into this the last few days and I'm surprised I haven't come across a solution yet. I dont feel like I'm doing anything too extraordinary but maybe someone has some suggestions. I'm open to any!

Icestorm0141
  • 662
  • 2
  • 9
  • 21

4 Answers4

2

I'm assuming that your intention is to combine the base class filter with the inheriting class's filter, and not simply throwing out the base class filter. If you do simply want to ignore the base class filter, you can simply override self.filteredItems as in haim770's answer.

Otherwise, there's a lot of ways to go about this. One way would be to do something like this:

//In RoleGridModel
var baseFilteredItems = self.filteredItems; //Keep a reference to the base class's implementation
self.filteredItems = ko.computed(function () {
    var baseItems = baseFilteredItems();
    return filterByDepartment(baseItems); //filtering logic goes here
});

This does essentially what you want and is the closest analog to "overriding" a computed.


Another way to do it, other than "overriding" the computed, would be to modify the base class to support arbitrary filters. Something like:

//BaseGridViewModel:

var filters = ko.observableArray([]);
self.addFilter = function(filterFunction) {
    filters.push(filterFunction);
}

self.filteredItems = ko.computed(function() {
    var items = self.items();
    filters().forEach(function (filter) {
        //filters could also be implemented to take individual items and return true or false whether that item should be filtered, but that's a side point
        items = filter(items);
    });
    return items;
});

var searchFilter = function (items) {
    var filteredItems = // filtering logic here
    return filteredItems;
};

self.addFilter(searchFilter);

and

//RoleGridModel
var departmentFilter = function (items) {
    var filteredItems = // filtering logic here
    return filteredItems;
}

self.addFilter(departmentFilter)

This is a little more readable in the inheriting classes, though it requires more logic in the base class.

Retsam
  • 30,909
  • 11
  • 68
  • 90
  • That is extremely helpful, I hadn't even thought of combining the base class filter along with the override. I think what I need is what haim770 posted. There seems to be something else going on though that causes the UI to break. Probably the binding to the kendo grid that is preventing it from working. But at least now I know the basic principles of how to do what I am looking for. Thanks! – Icestorm0141 Oct 29 '14 at 12:46
  • Very helpful answer! Wish I can upvote it multiple times! – Raj Pawan Gumdal Oct 06 '16 at 07:02
1

You need to override filteredItems:

self.filteredItems = ko.computed(filterOverride);

See Fiddle

haim770
  • 48,394
  • 7
  • 105
  • 133
  • This should work nicely. Getting some errors with my specific implementation (I think the kendo grid is trying to bind at the wrong time) but I think once I work that out this will work well. Thanks! – Icestorm0141 Oct 29 '14 at 12:48
0

For anyone else that might find Retsam's solution helpful - which I have since needed to use, there was a syntax error when declaring addFilter. The corrected sytnax is:

self.addFilter = function(filterFunction) {
    filters().push(filterFunction);
}

Also, because filters is an observableArray the items in it weren't being tracked. So each of the filter functions I passed in were originally computed observables because they had observables that they were dependent on. Example:

self.addFilter(function () {
//used to be wrapped in a computed observable
    if (self.searchTerm() == null || self.searchTerm() == "" || self.searchTerm().length < 3) {
        return self.items();
    }
    else {
        return ko.utils.arrayFilter(self.items(), function (item) {
            return item.Description().toLowerCase().indexOf(self.searchTerm().toLowerCase()) >= 0;
        });
    }
});

In this example, the first filter function is dependent on searchTerm() but since its being added to the filters array, which is just an observable array, the UI didn't update when searchTerm was updated. I had to manually trigger an update via subscriptions for each observable that needed to update the UI when filtered upon:

self.searchTerm.subscribe(function (newValue) {
    filters.valueHasMutated();
});

There may be a better way to do this, but I haven't yet found one.

Icestorm0141
  • 662
  • 2
  • 9
  • 21
-1

I would use a inheritance library for fast and reliable inheritance Like,

https://github.com/dotnetwise/Javascript-FastClass

For example your method does not support prototype

Anders
  • 17,306
  • 10
  • 76
  • 144